blob: 7dff7e959d9d78409166e8d616b48bbc6ab75f47 [file] [log] [blame]
Willy Tarreaubaaee002006-06-26 02:48:02 +02001/*
2 * Health-checks functions.
3 *
Willy Tarreau26c25062009-03-08 09:38:41 +01004 * Copyright 2000-2009 Willy Tarreau <w@1wt.eu>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +02005 * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
Willy Tarreaubaaee002006-06-26 02:48:02 +02006 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 */
13
Willy Tarreaub8816082008-01-18 12:18:15 +010014#include <assert.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020015#include <ctype.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020016#include <errno.h>
17#include <fcntl.h>
Willy Tarreau9b39dc52014-07-08 00:54:10 +020018#include <signal.h>
Simon Horman0ba0e4a2015-01-30 11:23:00 +090019#include <stdarg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020020#include <stdio.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020021#include <stdlib.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020022#include <string.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020023#include <time.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020024#include <unistd.h>
Willy Tarreau9f6dc722019-03-01 11:15:10 +010025#include <sys/resource.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020026#include <sys/socket.h>
Dmitry Sivachenkocaf58982009-08-24 15:11:06 +040027#include <sys/types.h>
Simon Horman98637e52014-06-20 12:30:16 +090028#include <sys/wait.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020029#include <netinet/in.h>
Willy Tarreau1274bc42009-07-15 07:16:31 +020030#include <netinet/tcp.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020031#include <arpa/inet.h>
32
Christopher Fauletfd6c2292020-03-25 18:20:15 +010033#include <common/cfgparse.h>
Willy Tarreauc7e42382012-08-24 19:22:53 +020034#include <common/chunk.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020035#include <common/compat.h>
36#include <common/config.h>
37#include <common/mini-clist.h>
Willy Tarreau83749182007-04-15 20:56:27 +020038#include <common/standard.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020039#include <common/time.h>
Christopher Fauletcfda8472017-10-20 15:40:23 +020040#include <common/hathreads.h>
Christopher Faulete5870d82020-04-15 11:32:03 +020041#include <common/http.h>
42#include <common/h1.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020043#include <common/htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020044
45#include <types/global.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020046#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010047#include <types/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020048
Gaetan Rivet707b52f2020-02-21 18:14:59 +010049#include <proto/action.h>
Christopher Fauletba3c68f2020-04-01 16:27:05 +020050#include <proto/arg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020051#include <proto/backend.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020052#include <proto/checks.h>
William Lallemand9ed62032016-11-21 17:49:11 +010053#include <proto/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020054#include <proto/fd.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020055#include <proto/http_htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020056#include <proto/log.h>
Willy Tarreau53a47662017-08-28 10:53:00 +020057#include <proto/mux_pt.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020058#include <proto/queue.h>
Willy Tarreauc6f4ce82009-06-10 11:09:37 +020059#include <proto/port_range.h>
Willy Tarreaue8c66af2008-01-13 18:40:14 +010060#include <proto/proto_tcp.h>
Baptiste Assmann69e273f2013-12-11 00:52:19 +010061#include <proto/protocol.h>
Willy Tarreau2b5652f2006-12-31 17:46:05 +010062#include <proto/proxy.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020063#include <proto/server.h>
Willy Tarreau48d6bf22016-06-21 16:27:34 +020064#include <proto/signal.h>
Willy Tarreau9e000c62011-03-10 14:03:36 +010065#include <proto/stream_interface.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020066#include <proto/task.h>
Gaetan Rivet13a50432020-02-21 18:13:44 +010067#include <proto/vars.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020068#include <proto/log.h>
69#include <proto/dns.h>
70#include <proto/proto_udp.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020071#include <proto/ssl_sock.h>
Christopher Fauletb7d30092020-03-30 15:19:03 +020072#include <proto/sample.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020073
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +020074static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *);
Christopher Faulet31c30fd2020-03-26 21:10:03 +010075
Christopher Faulet61cc8522020-04-20 14:54:42 +020076static int wake_srv_chk(struct conn_stream *cs);
77struct data_cb check_conn_cb = {
78 .wake = wake_srv_chk,
79 .name = "CHCK",
80};
Christopher Fauletd7e63962020-04-17 20:15:59 +020081
Christopher Fauletd7cee712020-04-21 13:45:00 +020082/* Global tree to share all tcp-checks */
83struct eb_root shared_tcpchecks = EB_ROOT;
Christopher Faulet5d503fc2020-03-30 20:34:34 +020084
85
Willy Tarreau8ceae722018-11-26 11:58:30 +010086DECLARE_STATIC_POOL(pool_head_email_alert, "email_alert", sizeof(struct email_alert));
87DECLARE_STATIC_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
Christopher Faulet31dff9b2017-10-23 15:45:20 +020088
Gaetan Rivet05d692d2020-02-14 17:42:54 +010089/* Dummy frontend used to create all checks sessions. */
90static struct proxy checks_fe;
Christopher Faulet31dff9b2017-10-23 15:45:20 +020091
Christopher Faulet61cc8522020-04-20 14:54:42 +020092/**************************************************************************/
93/************************ Handle check results ****************************/
94/**************************************************************************/
95struct check_status {
96 short result; /* one of SRV_CHK_* */
97 char *info; /* human readable short info */
98 char *desc; /* long description */
99};
100
101struct analyze_status {
102 char *desc; /* description */
103 unsigned char lr[HANA_OBS_SIZE]; /* result for l4/l7: 0 = ignore, 1 - error, 2 - OK */
104};
105
Simon Horman63a4a822012-03-19 07:24:41 +0900106static const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100107 [HCHK_STATUS_UNKNOWN] = { CHK_RES_UNKNOWN, "UNK", "Unknown" },
108 [HCHK_STATUS_INI] = { CHK_RES_UNKNOWN, "INI", "Initializing" },
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200109 [HCHK_STATUS_START] = { /* SPECIAL STATUS*/ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200110
Willy Tarreau23964182014-05-20 20:56:30 +0200111 /* Below we have finished checks */
112 [HCHK_STATUS_CHECKED] = { CHK_RES_NEUTRAL, "CHECKED", "No status change" },
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100113 [HCHK_STATUS_HANA] = { CHK_RES_FAILED, "HANA", "Health analyze" },
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100114
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100115 [HCHK_STATUS_SOCKERR] = { CHK_RES_FAILED, "SOCKERR", "Socket error" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200116
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100117 [HCHK_STATUS_L4OK] = { CHK_RES_PASSED, "L4OK", "Layer4 check passed" },
118 [HCHK_STATUS_L4TOUT] = { CHK_RES_FAILED, "L4TOUT", "Layer4 timeout" },
119 [HCHK_STATUS_L4CON] = { CHK_RES_FAILED, "L4CON", "Layer4 connection problem" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200120
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100121 [HCHK_STATUS_L6OK] = { CHK_RES_PASSED, "L6OK", "Layer6 check passed" },
122 [HCHK_STATUS_L6TOUT] = { CHK_RES_FAILED, "L6TOUT", "Layer6 timeout" },
123 [HCHK_STATUS_L6RSP] = { CHK_RES_FAILED, "L6RSP", "Layer6 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200124
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100125 [HCHK_STATUS_L7TOUT] = { CHK_RES_FAILED, "L7TOUT", "Layer7 timeout" },
126 [HCHK_STATUS_L7RSP] = { CHK_RES_FAILED, "L7RSP", "Layer7 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200127
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200128 [HCHK_STATUS_L57DATA] = { /* DUMMY STATUS */ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200129
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100130 [HCHK_STATUS_L7OKD] = { CHK_RES_PASSED, "L7OK", "Layer7 check passed" },
131 [HCHK_STATUS_L7OKCD] = { CHK_RES_CONDPASS, "L7OKC", "Layer7 check conditionally passed" },
132 [HCHK_STATUS_L7STS] = { CHK_RES_FAILED, "L7STS", "Layer7 wrong status" },
Simon Horman98637e52014-06-20 12:30:16 +0900133
134 [HCHK_STATUS_PROCERR] = { CHK_RES_FAILED, "PROCERR", "External check error" },
135 [HCHK_STATUS_PROCTOUT] = { CHK_RES_FAILED, "PROCTOUT", "External check timeout" },
Cyril Bonté77010d82014-08-07 01:55:37 +0200136 [HCHK_STATUS_PROCOK] = { CHK_RES_PASSED, "PROCOK", "External check passed" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200137};
138
Simon Horman63a4a822012-03-19 07:24:41 +0900139static const struct analyze_status analyze_statuses[HANA_STATUS_SIZE] = { /* 0: ignore, 1: error, 2: OK */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100140 [HANA_STATUS_UNKNOWN] = { "Unknown", { 0, 0 }},
141
142 [HANA_STATUS_L4_OK] = { "L4 successful connection", { 2, 0 }},
143 [HANA_STATUS_L4_ERR] = { "L4 unsuccessful connection", { 1, 1 }},
144
145 [HANA_STATUS_HTTP_OK] = { "Correct http response", { 0, 2 }},
146 [HANA_STATUS_HTTP_STS] = { "Wrong http response", { 0, 1 }},
147 [HANA_STATUS_HTTP_HDRRSP] = { "Invalid http response (headers)", { 0, 1 }},
148 [HANA_STATUS_HTTP_RSP] = { "Invalid http response", { 0, 1 }},
149
150 [HANA_STATUS_HTTP_READ_ERROR] = { "Read error (http)", { 0, 1 }},
151 [HANA_STATUS_HTTP_READ_TIMEOUT] = { "Read timeout (http)", { 0, 1 }},
152 [HANA_STATUS_HTTP_BROKEN_PIPE] = { "Close from server (http)", { 0, 1 }},
153};
154
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100155/* checks if <err> is a real error for errno or one that can be ignored, and
156 * return 0 for these ones or <err> for real ones.
157 */
158static inline int unclean_errno(int err)
159{
160 if (err == EAGAIN || err == EINPROGRESS ||
161 err == EISCONN || err == EALREADY)
162 return 0;
163 return err;
164}
165
Christopher Faulet61cc8522020-04-20 14:54:42 +0200166/* Converts check_status code to description */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200167const char *get_check_status_description(short check_status) {
168
169 const char *desc;
170
171 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200172 desc = check_statuses[check_status].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200173 else
174 desc = NULL;
175
176 if (desc && *desc)
177 return desc;
178 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200179 return check_statuses[HCHK_STATUS_UNKNOWN].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200180}
181
Christopher Faulet61cc8522020-04-20 14:54:42 +0200182/* Converts check_status code to short info */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200183const char *get_check_status_info(short check_status) {
184
185 const char *info;
186
187 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200188 info = check_statuses[check_status].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200189 else
190 info = NULL;
191
192 if (info && *info)
193 return info;
194 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200195 return check_statuses[HCHK_STATUS_UNKNOWN].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200196}
197
Christopher Faulet61cc8522020-04-20 14:54:42 +0200198/* Convert analyze_status to description */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100199const char *get_analyze_status(short analyze_status) {
200
201 const char *desc;
202
203 if (analyze_status < HANA_STATUS_SIZE)
204 desc = analyze_statuses[analyze_status].desc;
205 else
206 desc = NULL;
207
208 if (desc && *desc)
209 return desc;
210 else
211 return analyze_statuses[HANA_STATUS_UNKNOWN].desc;
212}
213
Christopher Faulet61cc8522020-04-20 14:54:42 +0200214/* Sets check->status, update check->duration and fill check->result with an
215 * adequate CHK_RES_* value. The new check->health is computed based on the
216 * result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200217 *
Christopher Faulet61cc8522020-04-20 14:54:42 +0200218 * Shows information in logs about failed health check if server is UP or
219 * succeeded health checks if server is DOWN.
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200220 */
Simon Horman4a741432013-02-23 15:35:38 +0900221static void set_server_check_status(struct check *check, short status, const char *desc)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100222{
Simon Horman4a741432013-02-23 15:35:38 +0900223 struct server *s = check->server;
Willy Tarreaubef1b322014-05-13 21:01:39 +0200224 short prev_status = check->status;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200225 int report = 0;
Simon Horman4a741432013-02-23 15:35:38 +0900226
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200227 if (status == HCHK_STATUS_START) {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100228 check->result = CHK_RES_UNKNOWN; /* no result yet */
Simon Horman4a741432013-02-23 15:35:38 +0900229 check->desc[0] = '\0';
230 check->start = now;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200231 return;
232 }
233
Simon Horman4a741432013-02-23 15:35:38 +0900234 if (!check->status)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200235 return;
236
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200237 if (desc && *desc) {
Simon Horman4a741432013-02-23 15:35:38 +0900238 strncpy(check->desc, desc, HCHK_DESC_LEN-1);
239 check->desc[HCHK_DESC_LEN-1] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200240 } else
Simon Horman4a741432013-02-23 15:35:38 +0900241 check->desc[0] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200242
Simon Horman4a741432013-02-23 15:35:38 +0900243 check->status = status;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200244 if (check_statuses[status].result)
Simon Horman4a741432013-02-23 15:35:38 +0900245 check->result = check_statuses[status].result;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200246
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100247 if (status == HCHK_STATUS_HANA)
Simon Horman4a741432013-02-23 15:35:38 +0900248 check->duration = -1;
249 else if (!tv_iszero(&check->start)) {
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200250 /* set_server_check_status() may be called more than once */
Simon Horman4a741432013-02-23 15:35:38 +0900251 check->duration = tv_ms_elapsed(&check->start, &now);
252 tv_zero(&check->start);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200253 }
254
Willy Tarreau23964182014-05-20 20:56:30 +0200255 /* no change is expected if no state change occurred */
256 if (check->result == CHK_RES_NEUTRAL)
257 return;
258
Olivier Houchard0923fa42019-01-11 18:43:04 +0100259 /* If the check was really just sending a mail, it won't have an
260 * associated server, so we're done now.
261 */
262 if (!s)
263 return;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200264 report = 0;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200265
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200266 switch (check->result) {
267 case CHK_RES_FAILED:
Willy Tarreau12634e12014-05-23 11:32:36 +0200268 /* Failure to connect to the agent as a secondary check should not
269 * cause the server to be marked down.
270 */
271 if ((!(check->state & CHK_ST_AGENT) ||
Simon Hormaneaabd522015-02-26 11:26:17 +0900272 (check->status >= HCHK_STATUS_L57DATA)) &&
Christopher Fauletb119a792018-05-02 12:12:45 +0200273 (check->health > 0)) {
Olivier Houchard7059c552019-03-08 18:49:32 +0100274 _HA_ATOMIC_ADD(&s->counters.failed_checks, 1);
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200275 report = 1;
276 check->health--;
277 if (check->health < check->rise)
278 check->health = 0;
279 }
280 break;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200281
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200282 case CHK_RES_PASSED:
283 case CHK_RES_CONDPASS: /* "condpass" cannot make the first step but it OK after a "passed" */
284 if ((check->health < check->rise + check->fall - 1) &&
285 (check->result == CHK_RES_PASSED || check->health > 0)) {
286 report = 1;
287 check->health++;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200288
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200289 if (check->health >= check->rise)
290 check->health = check->rise + check->fall - 1; /* OK now */
291 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200292
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200293 /* clear consecutive_errors if observing is enabled */
294 if (s->onerror)
295 s->consecutive_errors = 0;
296 break;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100297
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200298 default:
299 break;
300 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200301
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200302 if (s->proxy->options2 & PR_O2_LOGHCHKS &&
303 (status != prev_status || report)) {
304 chunk_printf(&trash,
Willy Tarreau12634e12014-05-23 11:32:36 +0200305 "%s check for %sserver %s/%s %s%s",
306 (check->state & CHK_ST_AGENT) ? "Agent" : "Health",
Willy Tarreauc93cd162014-05-13 15:54:22 +0200307 s->flags & SRV_F_BACKUP ? "backup " : "",
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100308 s->proxy->id, s->id,
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100309 (check->result == CHK_RES_CONDPASS) ? "conditionally ":"",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200310 (check->result >= CHK_RES_PASSED) ? "succeeded" : "failed");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200311
Emeric Brun5a133512017-10-19 14:42:30 +0200312 srv_append_status(&trash, s, check, -1, 0);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200313
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100314 chunk_appendf(&trash, ", status: %d/%d %s",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200315 (check->health >= check->rise) ? check->health - check->rise + 1 : check->health,
316 (check->health >= check->rise) ? check->fall : check->rise,
317 (check->health >= check->rise) ? (s->uweight ? "UP" : "DRAIN") : "DOWN");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200318
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200319 ha_warning("%s.\n", trash.area);
320 send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area);
321 send_email_alert(s, LOG_INFO, "%s", trash.area);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200322 }
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200323}
324
Willy Tarreau4eec5472014-05-20 22:32:27 +0200325/* Marks the check <check>'s server down if the current check is already failed
326 * and the server is not down yet nor in maintenance.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200327 */
Willy Tarreau4eec5472014-05-20 22:32:27 +0200328static void check_notify_failure(struct check *check)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200329{
Simon Horman4a741432013-02-23 15:35:38 +0900330 struct server *s = check->server;
Simon Hormane0d1bfb2011-06-21 14:34:58 +0900331
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200332 /* The agent secondary check should only cause a server to be marked
333 * as down if check->status is HCHK_STATUS_L7STS, which indicates
334 * that the agent returned "fail", "stopped" or "down".
335 * The implication here is that failure to connect to the agent
336 * as a secondary check should not cause the server to be marked
337 * down. */
338 if ((check->state & CHK_ST_AGENT) && check->status != HCHK_STATUS_L7STS)
339 return;
340
Willy Tarreau4eec5472014-05-20 22:32:27 +0200341 if (check->health > 0)
342 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100343
Willy Tarreau4eec5472014-05-20 22:32:27 +0200344 /* We only report a reason for the check if we did not do so previously */
Emeric Brun5a133512017-10-19 14:42:30 +0200345 srv_set_stopped(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200346}
347
Willy Tarreauaf549582014-05-16 17:37:50 +0200348/* Marks the check <check> as valid and tries to set its server up, provided
Willy Tarreau3e048382014-05-21 10:30:54 +0200349 * it isn't in maintenance, it is not tracking a down server and other checks
350 * comply. The rule is simple : by default, a server is up, unless any of the
351 * following conditions is true :
352 * - health check failed (check->health < rise)
353 * - agent check failed (agent->health < rise)
354 * - the server tracks a down server (track && track->state == STOPPED)
355 * Note that if the server has a slowstart, it will switch to STARTING instead
356 * of RUNNING. Also, only the health checks support the nolb mode, so the
357 * agent's success may not take the server out of this mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200358 */
Willy Tarreau3e048382014-05-21 10:30:54 +0200359static void check_notify_success(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200360{
Simon Horman4a741432013-02-23 15:35:38 +0900361 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100362
Emeric Brun52a91d32017-08-31 14:41:55 +0200363 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200364 return;
Cyril Bontécd19e512010-01-31 22:34:03 +0100365
Emeric Brun52a91d32017-08-31 14:41:55 +0200366 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreauaf549582014-05-16 17:37:50 +0200367 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100368
Willy Tarreau3e048382014-05-21 10:30:54 +0200369 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
370 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100371
Willy Tarreau3e048382014-05-21 10:30:54 +0200372 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
373 return;
Willy Tarreauaf549582014-05-16 17:37:50 +0200374
Emeric Brun52a91d32017-08-31 14:41:55 +0200375 if ((check->state & CHK_ST_AGENT) && s->next_state == SRV_ST_STOPPING)
Willy Tarreau3e048382014-05-21 10:30:54 +0200376 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100377
Emeric Brun5a133512017-10-19 14:42:30 +0200378 srv_set_running(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100379}
380
Willy Tarreaudb58b792014-05-21 13:57:23 +0200381/* Marks the check <check> as valid and tries to set its server into stopping mode
382 * if it was running or starting, and provided it isn't in maintenance and other
383 * checks comply. The conditions for the server to be marked in stopping mode are
384 * the same as for it to be turned up. Also, only the health checks support the
385 * nolb mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200386 */
Willy Tarreaudb58b792014-05-21 13:57:23 +0200387static void check_notify_stopping(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200388{
Simon Horman4a741432013-02-23 15:35:38 +0900389 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100390
Emeric Brun52a91d32017-08-31 14:41:55 +0200391 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200392 return;
393
Willy Tarreaudb58b792014-05-21 13:57:23 +0200394 if (check->state & CHK_ST_AGENT)
395 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100396
Emeric Brun52a91d32017-08-31 14:41:55 +0200397 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreaudb58b792014-05-21 13:57:23 +0200398 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100399
Willy Tarreaudb58b792014-05-21 13:57:23 +0200400 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
401 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100402
Willy Tarreaudb58b792014-05-21 13:57:23 +0200403 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
404 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100405
Willy Tarreaub26881a2017-12-23 11:16:49 +0100406 srv_set_stopping(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100407}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200408
Willy Tarreau9fe7aae2013-12-31 23:47:37 +0100409/* note: use health_adjust() only, which first checks that the observe mode is
410 * enabled.
411 */
412void __health_adjust(struct server *s, short status)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100413{
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100414 int failed;
415 int expire;
416
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100417 if (s->observe >= HANA_OBS_SIZE)
418 return;
419
Willy Tarreaubb956662013-01-24 00:37:39 +0100420 if (status >= HANA_STATUS_SIZE || !analyze_statuses[status].desc)
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100421 return;
422
423 switch (analyze_statuses[status].lr[s->observe - 1]) {
424 case 1:
425 failed = 1;
426 break;
427
428 case 2:
429 failed = 0;
430 break;
431
432 default:
433 return;
434 }
435
436 if (!failed) {
437 /* good: clear consecutive_errors */
438 s->consecutive_errors = 0;
439 return;
440 }
441
Olivier Houchard7059c552019-03-08 18:49:32 +0100442 _HA_ATOMIC_ADD(&s->consecutive_errors, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100443
444 if (s->consecutive_errors < s->consecutive_errors_limit)
445 return;
446
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100447 chunk_printf(&trash, "Detected %d consecutive errors, last one was: %s",
448 s->consecutive_errors, get_analyze_status(status));
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100449
450 switch (s->onerror) {
451 case HANA_ONERR_FASTINTER:
452 /* force fastinter - nothing to do here as all modes force it */
453 break;
454
455 case HANA_ONERR_SUDDTH:
456 /* simulate a pre-fatal failed health check */
Simon Horman58c32972013-11-25 10:46:38 +0900457 if (s->check.health > s->check.rise)
458 s->check.health = s->check.rise + 1;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100459
460 /* no break - fall through */
461
462 case HANA_ONERR_FAILCHK:
463 /* simulate a failed health check */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200464 set_server_check_status(&s->check, HCHK_STATUS_HANA,
465 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200466 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100467 break;
468
469 case HANA_ONERR_MARKDWN:
470 /* mark server down */
Simon Horman58c32972013-11-25 10:46:38 +0900471 s->check.health = s->check.rise;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200472 set_server_check_status(&s->check, HCHK_STATUS_HANA,
473 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200474 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100475 break;
476
477 default:
478 /* write a warning? */
479 break;
480 }
481
482 s->consecutive_errors = 0;
Olivier Houchard7059c552019-03-08 18:49:32 +0100483 _HA_ATOMIC_ADD(&s->counters.failed_hana, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100484
Simon Horman66183002013-02-23 10:16:43 +0900485 if (s->check.fastinter) {
486 expire = tick_add(now_ms, MS_TO_TICKS(s->check.fastinter));
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300487 if (s->check.task->expire > expire) {
Willy Tarreau5b3a2022012-09-28 15:01:02 +0200488 s->check.task->expire = expire;
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300489 /* requeue check task with new expire */
490 task_queue(s->check.task);
491 }
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100492 }
Willy Tarreauef781042010-01-27 11:53:01 +0100493}
494
Christopher Faulet61cc8522020-04-20 14:54:42 +0200495/* Checks the connection. If an error has already been reported or the socket is
Willy Tarreau20a18342013-12-05 00:31:46 +0100496 * closed, keep errno intact as it is supposed to contain the valid error code.
497 * If no error is reported, check the socket's error queue using getsockopt().
498 * Warning, this must be done only once when returning from poll, and never
499 * after an I/O error was attempted, otherwise the error queue might contain
500 * inconsistent errors. If an error is detected, the CO_FL_ERROR is set on the
501 * socket. Returns non-zero if an error was reported, zero if everything is
502 * clean (including a properly closed socket).
503 */
504static int retrieve_errno_from_socket(struct connection *conn)
505{
506 int skerr;
507 socklen_t lskerr = sizeof(skerr);
508
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100509 if (conn->flags & CO_FL_ERROR && (unclean_errno(errno) || !conn->ctrl))
Willy Tarreau20a18342013-12-05 00:31:46 +0100510 return 1;
511
Willy Tarreau3c728722014-01-23 13:50:42 +0100512 if (!conn_ctrl_ready(conn))
Willy Tarreau20a18342013-12-05 00:31:46 +0100513 return 0;
514
Willy Tarreau585744b2017-08-24 14:31:19 +0200515 if (getsockopt(conn->handle.fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == 0)
Willy Tarreau20a18342013-12-05 00:31:46 +0100516 errno = skerr;
517
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100518 errno = unclean_errno(errno);
Willy Tarreau20a18342013-12-05 00:31:46 +0100519
520 if (!errno) {
521 /* we could not retrieve an error, that does not mean there is
522 * none. Just don't change anything and only report the prior
523 * error if any.
524 */
525 if (conn->flags & CO_FL_ERROR)
526 return 1;
527 else
528 return 0;
529 }
530
531 conn->flags |= CO_FL_ERROR | CO_FL_SOCK_WR_SH | CO_FL_SOCK_RD_SH;
532 return 1;
533}
534
Christopher Faulet61cc8522020-04-20 14:54:42 +0200535/* Tries to collect as much information as possible on the connection status,
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100536 * and adjust the server status accordingly. It may make use of <errno_bck>
537 * if non-null when the caller is absolutely certain of its validity (eg:
538 * checked just after a syscall). If the caller doesn't have a valid errno,
539 * it can pass zero, and retrieve_errno_from_socket() will be called to try
540 * to extract errno from the socket. If no error is reported, it will consider
541 * the <expired> flag. This is intended to be used when a connection error was
542 * reported in conn->flags or when a timeout was reported in <expired>. The
543 * function takes care of not updating a server status which was already set.
544 * All situations where at least one of <expired> or CO_FL_ERROR are set
545 * produce a status.
546 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200547static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100548{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200549 struct conn_stream *cs = check->cs;
550 struct connection *conn = cs_conn(cs);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100551 const char *err_msg;
Willy Tarreau83061a82018-07-13 11:56:34 +0200552 struct buffer *chk;
Willy Tarreau213c6782014-10-02 14:51:02 +0200553 int step;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100554
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100555 if (check->result != CHK_RES_UNKNOWN)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100556 return;
557
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100558 errno = unclean_errno(errno_bck);
559 if (conn && errno)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100560 retrieve_errno_from_socket(conn);
561
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200562 if (conn && !(conn->flags & CO_FL_ERROR) &&
563 !(cs->flags & CS_FL_ERROR) && !expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100564 return;
565
566 /* we'll try to build a meaningful error message depending on the
567 * context of the error possibly present in conn->err_code, and the
568 * socket error possibly collected above. This is useful to know the
569 * exact step of the L6 layer (eg: SSL handshake).
570 */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200571 chk = get_trash_chunk();
572
Christopher Faulet799f3a42020-04-07 12:06:14 +0200573 if (check->type == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +0200574 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200575 step = tcpcheck_get_step_id(check, NULL);
Willy Tarreau213c6782014-10-02 14:51:02 +0200576 if (!step)
577 chunk_printf(chk, " at initial connection step of tcp-check");
578 else {
579 chunk_printf(chk, " at step %d of tcp-check", step);
580 /* we were looking for a string */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200581 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
582 if (check->current_step->connect.port)
583 chunk_appendf(chk, " (connect port %d)" ,check->current_step->connect.port);
Willy Tarreau213c6782014-10-02 14:51:02 +0200584 else
585 chunk_appendf(chk, " (connect)");
586 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200587 else if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
588 struct tcpcheck_expect *expect = &check->current_step->expect;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100589
590 switch (expect->type) {
591 case TCPCHK_EXPECT_STRING:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200592 chunk_appendf(chk, " (expect string '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100593 break;
594 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200595 chunk_appendf(chk, " (expect binary '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100596 break;
597 case TCPCHK_EXPECT_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200598 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100599 break;
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100600 case TCPCHK_EXPECT_REGEX_BINARY:
601 chunk_appendf(chk, " (expect binary regex)");
602 break;
Christopher Faulete5870d82020-04-15 11:32:03 +0200603 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200604 chunk_appendf(chk, " (expect HTTP status '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Christopher Faulete5870d82020-04-15 11:32:03 +0200605 break;
606 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
607 chunk_appendf(chk, " (expect HTTP status regex)");
608 break;
609 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200610 chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Christopher Faulete5870d82020-04-15 11:32:03 +0200611 break;
612 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
613 chunk_appendf(chk, " (expect HTTP body regex)");
614 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200615 case TCPCHK_EXPECT_CUSTOM:
616 chunk_appendf(chk, " (expect custom function)");
617 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100618 case TCPCHK_EXPECT_UNDEF:
619 chunk_appendf(chk, " (undefined expect!)");
620 break;
621 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200622 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200623 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200624 chunk_appendf(chk, " (send)");
625 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200626
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200627 if (check->current_step && check->current_step->comment)
628 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200629 }
630 }
631
Willy Tarreau00149122017-10-04 18:05:01 +0200632 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100633 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200634 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
635 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100636 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200637 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
638 chk->area);
639 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100640 }
641 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100642 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200643 chunk_printf(&trash, "%s%s", strerror(errno),
644 chk->area);
645 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100646 }
647 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200648 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100649 }
650 }
651
Willy Tarreau00149122017-10-04 18:05:01 +0200652 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200653 /* NOTE: this is reported after <fall> tries */
654 chunk_printf(chk, "No port available for the TCP connection");
655 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
656 }
657
Willy Tarreau00149122017-10-04 18:05:01 +0200658 if (!conn) {
659 /* connection allocation error before the connection was established */
660 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
661 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100662 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100663 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200664 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100665 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
666 else if (expired)
667 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200668
669 /*
670 * might be due to a server IP change.
671 * Let's trigger a DNS resolution if none are currently running.
672 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100673 if (check->server)
674 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200675
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100676 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100677 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100678 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200679 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100680 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
681 else if (expired)
682 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
683 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200684 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100685 /* I/O error after connection was established and before we could diagnose */
686 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
687 }
688 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200689 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
690
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100691 /* connection established but expired check */
Christopher Faulet811f78c2020-04-01 11:10:27 +0200692 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
693 tout = check->current_step->expect.tout_status;
694 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100695 }
696
697 return;
698}
699
Willy Tarreaubaaee002006-06-26 02:48:02 +0200700
Christopher Faulet61cc8522020-04-20 14:54:42 +0200701/**************************************************************************/
702/*************** Init/deinit tcp-check rules and ruleset ******************/
703/**************************************************************************/
704/* Releases memory allocated for a log-format string */
705static void free_tcpcheck_fmt(struct list *fmt)
Willy Tarreau20bea422012-07-06 12:00:49 +0200706{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200707 struct logformat_node *lf, *lfb;
Willy Tarreau2d351b62013-12-05 02:36:25 +0100708
Christopher Faulet61cc8522020-04-20 14:54:42 +0200709 list_for_each_entry_safe(lf, lfb, fmt, list) {
710 LIST_DEL(&lf->list);
711 release_sample_expr(lf->expr);
712 free(lf->arg);
713 free(lf);
Willy Tarreau2d351b62013-12-05 02:36:25 +0100714 }
Willy Tarreau20bea422012-07-06 12:00:49 +0200715}
716
Christopher Faulet61cc8522020-04-20 14:54:42 +0200717/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
718static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
Willy Tarreau2e993902011-10-31 11:53:20 +0100719{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200720 if (!hdr)
721 return;
Willy Tarreau4fc49a92019-05-05 06:54:22 +0200722
Christopher Faulet61cc8522020-04-20 14:54:42 +0200723 free_tcpcheck_fmt(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200724 istfree(&hdr->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200725 free(hdr);
Willy Tarreau2e993902011-10-31 11:53:20 +0100726}
727
Christopher Faulet61cc8522020-04-20 14:54:42 +0200728/* Releases memory allocated for an HTTP header list used in a tcp-check send
729 * rule
Willy Tarreau894c6422017-10-04 15:58:52 +0200730 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200731static void free_tcpcheck_http_hdrs(struct list *hdrs)
Willy Tarreau894c6422017-10-04 15:58:52 +0200732{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200733 struct tcpcheck_http_hdr *hdr, *bhdr;
Willy Tarreau894c6422017-10-04 15:58:52 +0200734
Christopher Faulet61cc8522020-04-20 14:54:42 +0200735 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
736 LIST_DEL(&hdr->list);
737 free_tcpcheck_http_hdr(hdr);
Willy Tarreau894c6422017-10-04 15:58:52 +0200738 }
Willy Tarreau894c6422017-10-04 15:58:52 +0200739}
740
Christopher Faulet61cc8522020-04-20 14:54:42 +0200741/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
742 * tcp-check was allocated using a memory pool (it is used to instantiate email
743 * alerts).
Christopher Faulet95226db2020-04-15 11:34:04 +0200744 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200745static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
Christopher Faulet95226db2020-04-15 11:34:04 +0200746{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200747 if (!rule)
748 return;
Christopher Faulet95226db2020-04-15 11:34:04 +0200749
Christopher Faulet61cc8522020-04-20 14:54:42 +0200750 free(rule->comment);
751 switch (rule->action) {
752 case TCPCHK_ACT_SEND:
753 switch (rule->send.type) {
754 case TCPCHK_SEND_STRING:
755 case TCPCHK_SEND_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200756 istfree(&rule->send.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200757 break;
758 case TCPCHK_SEND_STRING_LF:
759 case TCPCHK_SEND_BINARY_LF:
760 free_tcpcheck_fmt(&rule->send.fmt);
761 break;
762 case TCPCHK_SEND_HTTP:
763 free(rule->send.http.meth.str.area);
764 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200765 istfree(&rule->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200766 else
767 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200768 istfree(&rule->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200769 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
770 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200771 istfree(&rule->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200772 else
773 free_tcpcheck_fmt(&rule->send.http.body_fmt);
774 break;
775 case TCPCHK_SEND_UNDEF:
776 break;
777 }
778 break;
779 case TCPCHK_ACT_EXPECT:
780 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
781 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
782 release_sample_expr(rule->expect.status_expr);
783 switch (rule->expect.type) {
784 case TCPCHK_EXPECT_STRING:
785 case TCPCHK_EXPECT_BINARY:
786 case TCPCHK_EXPECT_HTTP_STATUS:
787 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200788 istfree(&rule->expect.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200789 break;
790 case TCPCHK_EXPECT_REGEX:
791 case TCPCHK_EXPECT_REGEX_BINARY:
792 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
793 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
794 regex_free(rule->expect.regex);
795 break;
796 case TCPCHK_EXPECT_CUSTOM:
797 case TCPCHK_EXPECT_UNDEF:
798 break;
799 }
800 break;
801 case TCPCHK_ACT_CONNECT:
802 free(rule->connect.sni);
803 free(rule->connect.alpn);
804 release_sample_expr(rule->connect.port_expr);
805 break;
806 case TCPCHK_ACT_COMMENT:
807 break;
808 case TCPCHK_ACT_ACTION_KW:
809 free(rule->action_kw.rule);
810 break;
Christopher Faulet95226db2020-04-15 11:34:04 +0200811 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200812
813 if (in_pool)
814 pool_free(pool_head_tcpcheck_rule, rule);
815 else
816 free(rule);
Christopher Faulet95226db2020-04-15 11:34:04 +0200817}
818
Christopher Faulet61cc8522020-04-20 14:54:42 +0200819/* Creates a tcp-check variable used in preset variables before executing a
820 * tcp-check ruleset.
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100821 */
Christopher Fauletb61caf42020-04-21 10:57:42 +0200822static struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100823{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200824 struct tcpcheck_var *var = NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100825
Christopher Faulet61cc8522020-04-20 14:54:42 +0200826 var = calloc(1, sizeof(*var));
827 if (var == NULL)
828 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100829
Christopher Fauletb61caf42020-04-21 10:57:42 +0200830 var->name = istdup(name);
831 if (!isttest(var->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200832 free(var);
833 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100834 }
Simon Horman98637e52014-06-20 12:30:16 +0900835
Christopher Faulet61cc8522020-04-20 14:54:42 +0200836 LIST_INIT(&var->list);
837 return var;
Simon Horman98637e52014-06-20 12:30:16 +0900838}
839
Christopher Faulet61cc8522020-04-20 14:54:42 +0200840/* Releases memory allocated for a preset tcp-check variable */
841static void free_tcpcheck_var(struct tcpcheck_var *var)
Simon Horman98637e52014-06-20 12:30:16 +0900842{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200843 if (!var)
844 return;
845
Christopher Fauletb61caf42020-04-21 10:57:42 +0200846 istfree(&var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200847 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
848 free(var->data.u.str.area);
849 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
850 free(var->data.u.meth.str.area);
851 free(var);
Simon Horman98637e52014-06-20 12:30:16 +0900852}
853
Christopher Faulet61cc8522020-04-20 14:54:42 +0200854/* Releases a list of preset tcp-check variables */
855static void free_tcpcheck_vars(struct list *vars)
Simon Horman98637e52014-06-20 12:30:16 +0900856{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200857 struct tcpcheck_var *var, *back;
Christopher Fauletcfda8472017-10-20 15:40:23 +0200858
Christopher Faulet61cc8522020-04-20 14:54:42 +0200859 list_for_each_entry_safe(var, back, vars, list) {
860 LIST_DEL(&var->list);
861 free_tcpcheck_var(var);
862 }
Simon Horman98637e52014-06-20 12:30:16 +0900863}
864
Christopher Faulet61cc8522020-04-20 14:54:42 +0200865/* Duplicate a list of preset tcp-check variables */
866int dup_tcpcheck_vars(struct list *dst, struct list *src)
Simon Horman98637e52014-06-20 12:30:16 +0900867{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200868 struct tcpcheck_var *var, *new = NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900869
Christopher Faulet61cc8522020-04-20 14:54:42 +0200870 list_for_each_entry(var, src, list) {
Christopher Fauletb61caf42020-04-21 10:57:42 +0200871 new = create_tcpcheck_var(var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200872 if (!new)
873 goto error;
874 new->data.type = var->data.type;
875 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
876 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
877 goto error;
878 if (var->data.type == SMP_T_STR)
879 new->data.u.str.area[new->data.u.str.data] = 0;
880 }
881 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
882 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
883 goto error;
884 new->data.u.str.area[new->data.u.str.data] = 0;
885 new->data.u.meth.meth = var->data.u.meth.meth;
886 }
887 else
888 new->data.u = var->data.u;
889 LIST_ADDQ(dst, &new->list);
890 }
891 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900892
Christopher Faulet61cc8522020-04-20 14:54:42 +0200893 error:
894 free(new);
895 return 0;
896}
Christopher Fauletcfda8472017-10-20 15:40:23 +0200897
Christopher Faulet61cc8522020-04-20 14:54:42 +0200898/* Looks for a shared tcp-check ruleset given its name. */
899static struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
900{
901 struct tcpcheck_ruleset *rs;
Christopher Fauletd7cee712020-04-21 13:45:00 +0200902 struct ebpt_node *node;
Simon Horman98637e52014-06-20 12:30:16 +0900903
Christopher Fauletd7cee712020-04-21 13:45:00 +0200904 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
905 if (node) {
906 rs = container_of(node, typeof(*rs), node);
907 return rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200908 }
909 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900910}
911
Christopher Faulet61cc8522020-04-20 14:54:42 +0200912/* Creates a new shared tcp-check ruleset */
913static struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
Simon Horman98637e52014-06-20 12:30:16 +0900914{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200915 struct tcpcheck_ruleset *rs;
Simon Horman98637e52014-06-20 12:30:16 +0900916
Christopher Faulet61cc8522020-04-20 14:54:42 +0200917 rs = calloc(1, sizeof(*rs));
918 if (rs == NULL)
919 return NULL;
920
Christopher Fauletd7cee712020-04-21 13:45:00 +0200921 rs->node.key = strdup(name);
922 if (rs->node.key == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200923 free(rs);
924 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900925 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200926
Christopher Faulet61cc8522020-04-20 14:54:42 +0200927 LIST_INIT(&rs->rules);
Christopher Fauletd7cee712020-04-21 13:45:00 +0200928 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200929 return rs;
Simon Horman98637e52014-06-20 12:30:16 +0900930}
931
Christopher Faulet61cc8522020-04-20 14:54:42 +0200932/* Releases memory allocated by a tcp-check ruleset. */
933static void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
Simon Horman98637e52014-06-20 12:30:16 +0900934{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200935 struct tcpcheck_rule *r, *rb;
936 if (!rs)
937 return;
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200938
Christopher Fauletd7cee712020-04-21 13:45:00 +0200939 ebpt_delete(&rs->node);
940 free(rs->node.key);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200941 list_for_each_entry_safe(r, rb, &rs->rules, list) {
942 LIST_DEL(&r->list);
943 free_tcpcheck(r, 0);
944 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200945 free(rs);
Simon Horman98637e52014-06-20 12:30:16 +0900946}
947
Christopher Faulet61cc8522020-04-20 14:54:42 +0200948
949/**************************************************************************/
950/**************** Everything about tcp-checks execution *******************/
951/**************************************************************************/
952/* Returns the id of a step in a tcp-check ruleset */
953static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200954{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200955 if (!rule)
956 rule = check->current_step;
Simon Horman98637e52014-06-20 12:30:16 +0900957
Christopher Faulet61cc8522020-04-20 14:54:42 +0200958 /* no last started step => first step */
959 if (!rule)
Simon Horman98637e52014-06-20 12:30:16 +0900960 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900961
Christopher Faulet61cc8522020-04-20 14:54:42 +0200962 /* last step is the first implicit connect */
963 if (rule->index == 0 &&
964 rule->action == TCPCHK_ACT_CONNECT &&
965 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
966 return 0;
Simon Horman98637e52014-06-20 12:30:16 +0900967
Christopher Faulet61cc8522020-04-20 14:54:42 +0200968 return rule->index + 1;
Simon Horman98637e52014-06-20 12:30:16 +0900969}
970
Christopher Faulet61cc8522020-04-20 14:54:42 +0200971/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
972 * NULL if none was found.
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100973 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200974static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100975{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200976 struct tcpcheck_rule *r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100977
Christopher Faulet61cc8522020-04-20 14:54:42 +0200978 list_for_each_entry(r, rules->list, list) {
979 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
980 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100981 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200982 return NULL;
983}
Cyril Bontéac92a062014-12-27 22:28:38 +0100984
Christopher Faulet61cc8522020-04-20 14:54:42 +0200985/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
986 * NULL if none was found.
987 */
988static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
989{
990 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +0100991
Christopher Faulet61cc8522020-04-20 14:54:42 +0200992 list_for_each_entry_rev(r, rules->list, list) {
993 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
994 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100995 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200996 return NULL;
997}
Cyril Bontéac92a062014-12-27 22:28:38 +0100998
Christopher Faulet61cc8522020-04-20 14:54:42 +0200999/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
1000 * <start> or NULL if non was found. If <start> is NULL, it relies on
1001 * get_first_tcpcheck_rule().
1002 */
1003static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
1004{
1005 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001006
Christopher Faulet61cc8522020-04-20 14:54:42 +02001007 if (!start)
1008 return get_first_tcpcheck_rule(rules);
Cyril Bontéac92a062014-12-27 22:28:38 +01001009
Christopher Faulet61cc8522020-04-20 14:54:42 +02001010 r = LIST_NEXT(&start->list, typeof(r), list);
1011 list_for_each_entry_from(r, rules->list, list) {
1012 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1013 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001014 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001015 return NULL;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001016}
Simon Horman98637e52014-06-20 12:30:16 +09001017
Simon Horman98637e52014-06-20 12:30:16 +09001018
Christopher Faulet61cc8522020-04-20 14:54:42 +02001019/* Creates info message when a tcp-check healthcheck fails on an expect rule */
1020static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1021 int match, struct ist info)
1022{
1023 struct sample *smp;
Simon Horman98637e52014-06-20 12:30:16 +09001024
Christopher Faulet61cc8522020-04-20 14:54:42 +02001025 /* Follows these step to produce the info message:
1026 * 1. if info field is already provided, copy it
1027 * 2. if the expect rule provides an onerror log-format string,
1028 * use it to produce the message
1029 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1030 * 4. Otherwise produce the generic tcp-check info message
1031 */
1032 if (istlen(info)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02001033 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001034 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001035 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001036 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
1037 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
1038 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001039 }
Simon Horman98637e52014-06-20 12:30:16 +09001040
Christopher Faulet61cc8522020-04-20 14:54:42 +02001041 if (check->type == PR_O2_TCPCHK_CHK &&
1042 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
1043 goto comment;
Simon Horman98637e52014-06-20 12:30:16 +09001044
Christopher Faulet61cc8522020-04-20 14:54:42 +02001045 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
1046 switch (rule->expect.type) {
1047 case TCPCHK_EXPECT_STRING:
1048 case TCPCHK_EXPECT_HTTP_STATUS:
1049 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02001050 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
Christopher Faulet61cc8522020-04-20 14:54:42 +02001051 tcpcheck_get_step_id(check, rule));
1052 break;
1053 case TCPCHK_EXPECT_BINARY:
1054 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
1055 break;
1056 case TCPCHK_EXPECT_REGEX:
1057 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
1058 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
1059 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
1060 break;
1061 case TCPCHK_EXPECT_REGEX_BINARY:
1062 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001063 break;
1064 case TCPCHK_EXPECT_CUSTOM:
1065 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
1066 break;
1067 case TCPCHK_EXPECT_UNDEF:
1068 /* Should never happen. */
1069 return;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001070 }
1071
Christopher Faulet61cc8522020-04-20 14:54:42 +02001072 comment:
1073 /* If the failing expect rule provides a comment, it is concatenated to
1074 * the info message.
1075 */
1076 if (rule->comment) {
1077 chunk_strcat(msg, " comment: ");
Christopher Faulet88d939c2020-04-22 15:32:11 +02001078 chunk_strcat(msg, rule->comment);
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001079 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001080
Christopher Faulet61cc8522020-04-20 14:54:42 +02001081 /* Finally, the check status code is set if the failing expect rule
1082 * defines a status expression.
1083 */
1084 if (rule->expect.status_expr) {
1085 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1086 rule->expect.status_expr, SMP_T_SINT);
1087 if (smp)
1088 check->code = smp->data.u.sint;
Cyril Bontéac92a062014-12-27 22:28:38 +01001089 }
Simon Horman98637e52014-06-20 12:30:16 +09001090
Christopher Faulet61cc8522020-04-20 14:54:42 +02001091 *(b_tail(msg)) = '\0';
1092}
Cyril Bontéac92a062014-12-27 22:28:38 +01001093
Christopher Faulet61cc8522020-04-20 14:54:42 +02001094/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
1095static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1096 struct ist info)
1097{
1098 struct sample *smp;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001099
Christopher Faulet61cc8522020-04-20 14:54:42 +02001100 /* Follows these step to produce the info message:
1101 * 1. if info field is already provided, copy it
1102 * 2. if the expect rule provides an onsucces log-format string,
1103 * use it to produce the message
1104 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1105 * 4. Otherwise produce the generic tcp-check info message
1106 */
1107 if (istlen(info))
Christopher Fauletb61caf42020-04-21 10:57:42 +02001108 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001109 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
1110 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
1111 &rule->expect.onsuccess_fmt);
1112 else if (check->type == PR_O2_TCPCHK_CHK &&
1113 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
1114 chunk_strcat(msg, "(tcp-check)");
Simon Horman98637e52014-06-20 12:30:16 +09001115
Christopher Faulet61cc8522020-04-20 14:54:42 +02001116 /* Finally, the check status code is set if the expect rule defines a
1117 * status expression.
1118 */
1119 if (rule->expect.status_expr) {
1120 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1121 rule->expect.status_expr, SMP_T_SINT);
1122 if (smp)
1123 check->code = smp->data.u.sint;
Simon Horman98637e52014-06-20 12:30:16 +09001124 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001125
1126 *(b_tail(msg)) = '\0';
Simon Horman98637e52014-06-20 12:30:16 +09001127}
1128
Christopher Faulet61cc8522020-04-20 14:54:42 +02001129/* Builds the server state header used by HTTP health-checks */
1130static int httpchk_build_status_header(struct server *s, struct buffer *buf)
Simon Horman98637e52014-06-20 12:30:16 +09001131{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001132 int sv_state;
1133 int ratio;
1134 char addr[46];
1135 char port[6];
1136 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
1137 "UP %d/%d", "UP",
1138 "NOLB %d/%d", "NOLB",
1139 "no check" };
Simon Horman98637e52014-06-20 12:30:16 +09001140
Christopher Faulet61cc8522020-04-20 14:54:42 +02001141 if (!(s->check.state & CHK_ST_ENABLED))
1142 sv_state = 6;
1143 else if (s->cur_state != SRV_ST_STOPPED) {
1144 if (s->check.health == s->check.rise + s->check.fall - 1)
1145 sv_state = 3; /* UP */
1146 else
1147 sv_state = 2; /* going down */
Simon Horman98637e52014-06-20 12:30:16 +09001148
Christopher Faulet61cc8522020-04-20 14:54:42 +02001149 if (s->cur_state == SRV_ST_STOPPING)
1150 sv_state += 2;
1151 } else {
1152 if (s->check.health)
1153 sv_state = 1; /* going up */
1154 else
1155 sv_state = 0; /* DOWN */
Simon Horman98637e52014-06-20 12:30:16 +09001156 }
Willy Tarreaub7b24782016-06-21 15:32:29 +02001157
Christopher Faulet61cc8522020-04-20 14:54:42 +02001158 chunk_appendf(buf, srv_hlt_st[sv_state],
1159 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
1160 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreaub7b24782016-06-21 15:32:29 +02001161
Christopher Faulet61cc8522020-04-20 14:54:42 +02001162 addr_to_str(&s->addr, addr, sizeof(addr));
1163 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1164 snprintf(port, sizeof(port), "%u", s->svc_port);
1165 else
1166 *port = 0;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001167
Christopher Faulet61cc8522020-04-20 14:54:42 +02001168 chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
1169 addr, port, s->proxy->id, s->id,
1170 global.node,
1171 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1172 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1173 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
1174 s->nbpend);
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001175
Christopher Faulet61cc8522020-04-20 14:54:42 +02001176 if ((s->cur_state == SRV_ST_STARTING) &&
1177 now.tv_sec < s->last_change + s->slowstart &&
1178 now.tv_sec >= s->last_change) {
1179 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
1180 chunk_appendf(buf, "; throttle=%d%%", ratio);
1181 }
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001182
Christopher Faulet61cc8522020-04-20 14:54:42 +02001183 return b_data(buf);
1184}
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001185
Christopher Faulet61cc8522020-04-20 14:54:42 +02001186/* Internal functions to parse and validate a MySQL packet in the context of an
1187 * expect rule. It start to parse the input buffer at the offset <offset>. If
1188 * <last_read> is set, no more data are expected.
1189 */
1190static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
1191 unsigned int offset, int last_read)
1192{
1193 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1194 enum healthcheck_status status;
1195 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001196 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001197 unsigned int err = 0, plen = 0;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001198
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001199
Christopher Faulet61cc8522020-04-20 14:54:42 +02001200 /* 3 Bytes for the packet length and 1 byte for the sequence id */
1201 if (!last_read && b_data(&check->bi) < offset+4) {
1202 if (!last_read)
1203 goto wait_more_data;
1204
1205 /* invalid length or truncated response */
1206 status = HCHK_STATUS_L7RSP;
1207 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001208 }
1209
Christopher Faulet61cc8522020-04-20 14:54:42 +02001210 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
1211 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
1212 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001213
Christopher Faulet61cc8522020-04-20 14:54:42 +02001214 if (b_data(&check->bi) < offset+plen+4) {
1215 if (!last_read)
1216 goto wait_more_data;
1217
1218 /* invalid length or truncated response */
1219 status = HCHK_STATUS_L7RSP;
1220 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001221 }
Simon Horman98637e52014-06-20 12:30:16 +09001222
Christopher Faulet61cc8522020-04-20 14:54:42 +02001223 if (*b_peek(&check->bi, offset+4) == '\xff') {
1224 /* MySQL Error packet always begin with field_count = 0xff */
1225 status = HCHK_STATUS_L7STS;
1226 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
1227 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
1228 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
1229 goto error;
1230 }
Simon Horman98637e52014-06-20 12:30:16 +09001231
Christopher Faulet61cc8522020-04-20 14:54:42 +02001232 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
1233 /* Not the last rule, continue */
1234 goto out;
1235 }
Simon Horman98637e52014-06-20 12:30:16 +09001236
Christopher Faulet61cc8522020-04-20 14:54:42 +02001237 /* We set the MySQL Version in description for information purpose
1238 * FIXME : it can be cool to use MySQL Version for other purpose,
1239 * like mark as down old MySQL server.
1240 */
1241 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Simon Horman98637e52014-06-20 12:30:16 +09001242
Christopher Faulet61cc8522020-04-20 14:54:42 +02001243 out:
1244 free_trash_chunk(msg);
1245 return ret;
Simon Horman98637e52014-06-20 12:30:16 +09001246
Christopher Faulet61cc8522020-04-20 14:54:42 +02001247 error:
1248 ret = TCPCHK_EVAL_STOP;
1249 check->code = err;
1250 msg = alloc_trash_chunk();
1251 if (msg)
1252 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1253 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1254 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001255
Christopher Faulet61cc8522020-04-20 14:54:42 +02001256 wait_more_data:
1257 ret = TCPCHK_EVAL_WAIT;
1258 goto out;
1259}
Simon Horman98637e52014-06-20 12:30:16 +09001260
Christopher Faulet61cc8522020-04-20 14:54:42 +02001261/* Custom tcp-check expect function to parse and validate the MySQL initial
1262 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1263 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1264 * error occurred.
1265 */
1266static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
1267{
1268 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
1269}
Simon Horman98637e52014-06-20 12:30:16 +09001270
Christopher Faulet61cc8522020-04-20 14:54:42 +02001271/* Custom tcp-check expect function to parse and validate the MySQL OK packet
1272 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
1273 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
1274 * an error occurred.
1275 */
1276static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
1277{
1278 unsigned int hslen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001279
Christopher Faulet61cc8522020-04-20 14:54:42 +02001280 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
1281 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
1282 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001283
Christopher Faulet61cc8522020-04-20 14:54:42 +02001284 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
1285}
Simon Horman98637e52014-06-20 12:30:16 +09001286
Christopher Faulet61cc8522020-04-20 14:54:42 +02001287/* Custom tcp-check expect function to parse and validate the LDAP bind response
1288 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1289 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1290 * error occurred.
1291 */
1292static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
1293{
1294 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1295 enum healthcheck_status status;
1296 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001297 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001298 unsigned short msglen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001299
Christopher Faulet61cc8522020-04-20 14:54:42 +02001300 /* Check if the server speaks LDAP (ASN.1/BER)
1301 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
1302 * http://tools.ietf.org/html/rfc4511
1303 */
1304 /* size of LDAPMessage */
1305 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001306
Christopher Faulet61cc8522020-04-20 14:54:42 +02001307 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
1308 * messageID: 0x02 0x01 0x01: INTEGER 1
1309 * protocolOp: 0x61: bindResponse
1310 */
1311 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
1312 status = HCHK_STATUS_L7RSP;
1313 desc = ist("Not LDAPv3 protocol");
1314 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001315 }
Simon Horman98637e52014-06-20 12:30:16 +09001316
Christopher Faulet61cc8522020-04-20 14:54:42 +02001317 /* size of bindResponse */
1318 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001319
Christopher Faulet61cc8522020-04-20 14:54:42 +02001320 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1321 * ldapResult: 0x0a 0x01: ENUMERATION
1322 */
1323 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
1324 status = HCHK_STATUS_L7RSP;
1325 desc = ist("Not LDAPv3 protocol");
1326 goto error;
1327 }
Simon Horman98637e52014-06-20 12:30:16 +09001328
Christopher Faulet61cc8522020-04-20 14:54:42 +02001329 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1330 * resultCode
1331 */
1332 check->code = *(b_head(&check->bi) + msglen + 9);
1333 if (check->code) {
1334 status = HCHK_STATUS_L7STS;
1335 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
1336 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001337 }
1338
Christopher Faulet61cc8522020-04-20 14:54:42 +02001339 set_server_check_status(check, rule->expect.ok_status, "Success");
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001340
Christopher Faulet61cc8522020-04-20 14:54:42 +02001341 out:
1342 free_trash_chunk(msg);
1343 return ret;
1344
1345 error:
1346 ret = TCPCHK_EVAL_STOP;
1347 msg = alloc_trash_chunk();
1348 if (msg)
1349 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1350 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1351 goto out;
1352
1353 wait_more_data:
1354 ret = TCPCHK_EVAL_WAIT;
1355 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001356}
1357
Christopher Faulet61cc8522020-04-20 14:54:42 +02001358/* Custom tcp-check expect function to parse and validate the SPOP hello agent
1359 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1360 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
Simon Horman98637e52014-06-20 12:30:16 +09001361 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001362static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001363{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001364 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1365 enum healthcheck_status status;
1366 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001367 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001368 unsigned int framesz;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001369
Willy Tarreaubaaee002006-06-26 02:48:02 +02001370
Christopher Faulet61cc8522020-04-20 14:54:42 +02001371 memcpy(&framesz, b_head(&check->bi), 4);
1372 framesz = ntohl(framesz);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001373
Christopher Faulet61cc8522020-04-20 14:54:42 +02001374 if (!last_read && b_data(&check->bi) < (4+framesz))
1375 goto wait_more_data;
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001376
Christopher Faulet61cc8522020-04-20 14:54:42 +02001377 memset(b_orig(&trash), 0, b_size(&trash));
1378 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
1379 status = HCHK_STATUS_L7RSP;
1380 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
1381 goto error;
1382 }
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001383
Christopher Faulet61cc8522020-04-20 14:54:42 +02001384 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Willy Tarreaubaaee002006-06-26 02:48:02 +02001385
Christopher Faulet61cc8522020-04-20 14:54:42 +02001386 out:
1387 free_trash_chunk(msg);
1388 return ret;
Christopher Fauleta32a2502020-04-20 09:04:37 +02001389
Christopher Faulet61cc8522020-04-20 14:54:42 +02001390 error:
1391 ret = TCPCHK_EVAL_STOP;
1392 msg = alloc_trash_chunk();
1393 if (msg)
1394 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1395 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1396 goto out;
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001397
Christopher Faulet61cc8522020-04-20 14:54:42 +02001398 wait_more_data:
1399 ret = TCPCHK_EVAL_WAIT;
1400 goto out;
1401}
Willy Tarreaubaaee002006-06-26 02:48:02 +02001402
Christopher Faulet61cc8522020-04-20 14:54:42 +02001403/* Custom tcp-check expect function to parse and validate the agent-check
1404 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1405 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
1406 */
1407static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
1408{
1409 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
1410 enum healthcheck_status status = HCHK_STATUS_CHECKED;
1411 const char *hs = NULL; /* health status */
1412 const char *as = NULL; /* admin status */
1413 const char *ps = NULL; /* performance status */
1414 const char *cs = NULL; /* maxconn */
1415 const char *err = NULL; /* first error to report */
1416 const char *wrn = NULL; /* first warning to report */
1417 char *cmd, *p;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001418
Christopher Faulet61cc8522020-04-20 14:54:42 +02001419 /* We're getting an agent check response. The agent could
1420 * have been disabled in the mean time with a long check
1421 * still pending. It is important that we ignore the whole
1422 * response.
1423 */
1424 if (!(check->state & CHK_ST_ENABLED))
1425 goto out;
Willy Tarreauf1503172012-09-28 19:39:36 +02001426
Christopher Faulet61cc8522020-04-20 14:54:42 +02001427 /* The agent supports strings made of a single line ended by the
1428 * first CR ('\r') or LF ('\n'). This line is composed of words
1429 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
1430 * line may optionally contained a description of a state change
1431 * after a sharp ('#'), which is only considered if a health state
1432 * is announced.
1433 *
1434 * Words may be composed of :
1435 * - a numeric weight suffixed by the percent character ('%').
1436 * - a health status among "up", "down", "stopped", and "fail".
1437 * - an admin status among "ready", "drain", "maint".
1438 *
1439 * These words may appear in any order. If multiple words of the
1440 * same category appear, the last one wins.
1441 */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001442
Christopher Faulet61cc8522020-04-20 14:54:42 +02001443 p = b_head(&check->bi);
1444 while (*p && *p != '\n' && *p != '\r')
1445 p++;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001446
Christopher Faulet61cc8522020-04-20 14:54:42 +02001447 if (!*p) {
1448 if (!last_read)
1449 goto wait_more_data;
1450
1451 /* at least inform the admin that the agent is mis-behaving */
1452 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
1453 goto out;
1454 }
1455
1456 *p = 0;
1457 cmd = b_head(&check->bi);
1458
1459 while (*cmd) {
1460 /* look for next word */
1461 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1462 cmd++;
1463 continue;
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001464 }
1465
Christopher Faulet61cc8522020-04-20 14:54:42 +02001466 if (*cmd == '#') {
1467 /* this is the beginning of a health status description,
1468 * skip the sharp and blanks.
Willy Tarreau86eded62019-06-14 14:47:49 +02001469 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001470 cmd++;
1471 while (*cmd == '\t' || *cmd == ' ')
1472 cmd++;
1473 break;
Willy Tarreau00149122017-10-04 18:05:01 +02001474 }
1475
Christopher Faulet61cc8522020-04-20 14:54:42 +02001476 /* find the end of the word so that we have a null-terminated
1477 * word between <cmd> and <p>.
1478 */
1479 p = cmd + 1;
1480 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1481 p++;
1482 if (*p)
1483 *p++ = 0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001484
Christopher Faulet61cc8522020-04-20 14:54:42 +02001485 /* first, health statuses */
1486 if (strcasecmp(cmd, "up") == 0) {
1487 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
1488 status = HCHK_STATUS_L7OKD;
1489 hs = cmd;
1490 }
1491 else if (strcasecmp(cmd, "down") == 0) {
1492 check->server->check.health = 0;
1493 status = HCHK_STATUS_L7STS;
1494 hs = cmd;
1495 }
1496 else if (strcasecmp(cmd, "stopped") == 0) {
1497 check->server->check.health = 0;
1498 status = HCHK_STATUS_L7STS;
1499 hs = cmd;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001500 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001501 else if (strcasecmp(cmd, "fail") == 0) {
1502 check->server->check.health = 0;
1503 status = HCHK_STATUS_L7STS;
1504 hs = cmd;
1505 }
1506 /* admin statuses */
1507 else if (strcasecmp(cmd, "ready") == 0) {
1508 as = cmd;
1509 }
1510 else if (strcasecmp(cmd, "drain") == 0) {
1511 as = cmd;
1512 }
1513 else if (strcasecmp(cmd, "maint") == 0) {
1514 as = cmd;
1515 }
1516 /* try to parse a weight here and keep the last one */
1517 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1518 ps = cmd;
1519 }
1520 /* try to parse a maxconn here */
1521 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1522 cs = cmd;
1523 }
1524 else {
1525 /* keep a copy of the first error */
1526 if (!err)
1527 err = cmd;
1528 }
1529 /* skip to next word */
1530 cmd = p;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001531 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001532 /* here, cmd points either to \0 or to the beginning of a
1533 * description. Skip possible leading spaces.
1534 */
1535 while (*cmd == ' ' || *cmd == '\n')
1536 cmd++;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001537
Christopher Faulet61cc8522020-04-20 14:54:42 +02001538 /* First, update the admin status so that we avoid sending other
1539 * possibly useless warnings and can also update the health if
1540 * present after going back up.
1541 */
1542 if (as) {
1543 if (strcasecmp(as, "drain") == 0)
1544 srv_adm_set_drain(check->server);
1545 else if (strcasecmp(as, "maint") == 0)
1546 srv_adm_set_maint(check->server);
1547 else
1548 srv_adm_set_ready(check->server);
1549 }
Simon Horman98637e52014-06-20 12:30:16 +09001550
Christopher Faulet61cc8522020-04-20 14:54:42 +02001551 /* now change weights */
1552 if (ps) {
1553 const char *msg;
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001554
Christopher Faulet61cc8522020-04-20 14:54:42 +02001555 msg = server_parse_weight_change_request(check->server, ps);
1556 if (!wrn || !*wrn)
1557 wrn = msg;
1558 }
Simon Horman98637e52014-06-20 12:30:16 +09001559
Christopher Faulet61cc8522020-04-20 14:54:42 +02001560 if (cs) {
1561 const char *msg;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001562
Christopher Faulet61cc8522020-04-20 14:54:42 +02001563 cs += strlen("maxconn:");
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001564
Christopher Faulet61cc8522020-04-20 14:54:42 +02001565 msg = server_parse_maxconn_change_request(check->server, cs);
1566 if (!wrn || !*wrn)
1567 wrn = msg;
Simon Horman5c942422013-11-25 10:46:32 +09001568 }
1569
Christopher Faulet61cc8522020-04-20 14:54:42 +02001570 /* and finally health status */
1571 if (hs) {
1572 /* We'll report some of the warnings and errors we have
1573 * here. Down reports are critical, we leave them untouched.
1574 * Lack of report, or report of 'UP' leaves the room for
1575 * ERR first, then WARN.
1576 */
1577 const char *msg = cmd;
1578 struct buffer *t;
Simon Horman5c942422013-11-25 10:46:32 +09001579
Christopher Faulet61cc8522020-04-20 14:54:42 +02001580 if (!*msg || status == HCHK_STATUS_L7OKD) {
1581 if (err && *err)
1582 msg = err;
1583 else if (wrn && *wrn)
1584 msg = wrn;
1585 }
Willy Tarreau1746eec2014-04-25 10:46:47 +02001586
Christopher Faulet61cc8522020-04-20 14:54:42 +02001587 t = get_trash_chunk();
1588 chunk_printf(t, "via agent : %s%s%s%s",
1589 hs, *msg ? " (" : "",
1590 msg, *msg ? ")" : "");
1591 set_server_check_status(check, status, t->area);
1592 }
1593 else if (err && *err) {
1594 /* No status change but we'd like to report something odd.
1595 * Just report the current state and copy the message.
1596 */
1597 chunk_printf(&trash, "agent reports an error : %s", err);
1598 set_server_check_status(check, status/*check->status*/, trash.area);
1599 }
1600 else if (wrn && *wrn) {
1601 /* No status change but we'd like to report something odd.
1602 * Just report the current state and copy the message.
1603 */
1604 chunk_printf(&trash, "agent warns : %s", wrn);
1605 set_server_check_status(check, status/*check->status*/, trash.area);
1606 }
1607 else
1608 set_server_check_status(check, status, NULL);
Willy Tarreau1746eec2014-04-25 10:46:47 +02001609
Christopher Faulet61cc8522020-04-20 14:54:42 +02001610 out:
1611 return ret;
Simon Horman5c942422013-11-25 10:46:32 +09001612
Christopher Faulet61cc8522020-04-20 14:54:42 +02001613 wait_more_data:
1614 ret = TCPCHK_EVAL_WAIT;
1615 goto out;
Simon Horman5c942422013-11-25 10:46:32 +09001616}
1617
Christopher Faulet61cc8522020-04-20 14:54:42 +02001618/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1619 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1620 * TCPCHK_EVAL_STOP if an error occurred.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001621 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001622static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau865c5142016-12-21 20:04:48 +01001623{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001624 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1625 struct tcpcheck_connect *connect = &rule->connect;
1626 struct proxy *proxy = check->proxy;
1627 struct server *s = check->server;
1628 struct task *t = check->task;
1629 struct conn_stream *cs;
1630 struct connection *conn = NULL;
1631 struct protocol *proto;
1632 struct xprt_ops *xprt;
1633 int status, port;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001634
Christopher Faulet61cc8522020-04-20 14:54:42 +02001635 /* For a connect action we'll create a new connection. We may also have
1636 * to kill a previous one. But we don't want to leave *without* a
1637 * connection if we came here from the connection layer, hence with a
1638 * connection. Thus we'll proceed in the following order :
1639 * 1: close but not release previous connection (handled by the caller)
1640 * 2: try to get a new connection
1641 * 3: release and replace the old one on success
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02001642 */
Willy Tarreaue7b73482013-11-21 11:50:50 +01001643
Christopher Faulet61cc8522020-04-20 14:54:42 +02001644 /* 2- prepare new connection */
1645 cs = cs_new(NULL);
1646 if (!cs) {
1647 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1648 tcpcheck_get_step_id(check, rule));
1649 if (rule->comment)
1650 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1651 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1652 ret = TCPCHK_EVAL_STOP;
1653 goto out;
1654 }
Willy Tarreau15f39102013-12-11 20:41:18 +01001655
Christopher Faulet61cc8522020-04-20 14:54:42 +02001656 /* 3- release and replace the old one on success */
1657 if (check->cs) {
1658 if (check->wait_list.events)
1659 cs->conn->xprt->unsubscribe(cs->conn, cs->conn->xprt_ctx,
1660 check->wait_list.events, &check->wait_list);
1661
1662 /* We may have been scheduled to run, and the I/O handler
1663 * expects to have a cs, so remove the tasklet
1664 */
1665 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
1666 cs_destroy(check->cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001667 }
1668
Christopher Faulet61cc8522020-04-20 14:54:42 +02001669 tasklet_set_tid(check->wait_list.tasklet, tid);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001670
Christopher Faulet61cc8522020-04-20 14:54:42 +02001671 check->cs = cs;
1672 conn = cs->conn;
1673 conn_set_owner(conn, check->sess, NULL);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001674
Christopher Faulet61cc8522020-04-20 14:54:42 +02001675 /* Maybe there were an older connection we were waiting on */
1676 check->wait_list.events = 0;
1677 conn->target = s ? &s->obj_type : &proxy->obj_type;
1678
1679 /* no client address */
1680 if (!sockaddr_alloc(&conn->dst)) {
1681 status = SF_ERR_RESOURCE;
1682 goto fail_check;
1683 }
1684
1685 /* connect to the connect rule addr if specified, otherwise the check
1686 * addr if specified on the server. otherwise, use the server addr
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001687 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001688 *conn->dst = (is_addr(&connect->addr)
1689 ? connect->addr
1690 : (is_addr(&check->addr) ? check->addr : s->addr));
1691 proto = protocol_by_family(conn->dst->ss_family);
Simon Horman98637e52014-06-20 12:30:16 +09001692
Christopher Faulet61cc8522020-04-20 14:54:42 +02001693 port = 0;
1694 if (!port && connect->port)
1695 port = connect->port;
1696 if (!port && connect->port_expr) {
1697 struct sample *smp;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001698
Christopher Faulet61cc8522020-04-20 14:54:42 +02001699 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1700 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1701 connect->port_expr, SMP_T_SINT);
1702 if (smp)
1703 port = smp->data.u.sint;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001704 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001705 if (!port && is_inet_addr(&connect->addr))
1706 port = get_host_port(&connect->addr);
1707 if (!port && check->port)
1708 port = check->port;
1709 if (!port && is_inet_addr(&check->addr))
1710 port = get_host_port(&check->addr);
1711 if (!port)
1712 port = s->svc_port;
1713 set_host_port(conn->dst, port);
Willy Tarreau213c6782014-10-02 14:51:02 +02001714
Christopher Faulet61cc8522020-04-20 14:54:42 +02001715 xprt = ((connect->options & TCPCHK_OPT_SSL)
1716 ? xprt_get(XPRT_SSL)
1717 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001718
Christopher Faulet61cc8522020-04-20 14:54:42 +02001719 conn_prepare(conn, proto, xprt);
1720 cs_attach(cs, check, &check_conn_cb);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001721
Christopher Faulet61cc8522020-04-20 14:54:42 +02001722 status = SF_ERR_INTERNAL;
1723 if (proto && proto->connect) {
1724 struct tcpcheck_rule *next;
1725 int flags = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001726
Christopher Faulet61cc8522020-04-20 14:54:42 +02001727 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1728 flags |= CONNECT_HAS_DATA;
Christopher Faulet206368d2020-04-03 14:51:06 +02001729
Christopher Faulet61cc8522020-04-20 14:54:42 +02001730 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1731 if (!next || next->action != TCPCHK_ACT_EXPECT)
1732 flags |= CONNECT_DELACK_ALWAYS;
1733 status = proto->connect(conn, flags);
Christopher Faulet206368d2020-04-03 14:51:06 +02001734 }
1735
Christopher Faulet61cc8522020-04-20 14:54:42 +02001736 if (status != SF_ERR_NONE)
1737 goto fail_check;
Christopher Faulet799f3a42020-04-07 12:06:14 +02001738
Christopher Faulet61cc8522020-04-20 14:54:42 +02001739 conn->flags |= CO_FL_PRIVATE;
1740 conn->ctx = cs;
Christopher Faulet206368d2020-04-03 14:51:06 +02001741
Christopher Faulet61cc8522020-04-20 14:54:42 +02001742 /* The mux may be initialized now if there isn't server attached to the
1743 * check (email alerts) or if there is a mux proto specified or if there
1744 * is no alpn.
1745 */
1746 if (!s || connect->mux_proto || check->mux_proto || (!connect->alpn && !check->alpn_str)) {
1747 const struct mux_ops *mux_ops;
Christopher Faulet206368d2020-04-03 14:51:06 +02001748
Christopher Faulet61cc8522020-04-20 14:54:42 +02001749 if (connect->mux_proto)
1750 mux_ops = connect->mux_proto->mux;
1751 else if (check->mux_proto)
1752 mux_ops = check->mux_proto->mux;
1753 else {
1754 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1755 ? PROTO_MODE_HTTP
1756 : PROTO_MODE_TCP);
1757
1758 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
Christopher Faulet206368d2020-04-03 14:51:06 +02001759 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001760 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1761 status = SF_ERR_INTERNAL;
1762 goto fail_check;
1763 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001764 }
1765
Christopher Faulet61cc8522020-04-20 14:54:42 +02001766#ifdef USE_OPENSSL
1767 if (connect->sni)
1768 ssl_sock_set_servername(conn, connect->sni);
1769 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
1770 ssl_sock_set_servername(conn, s->check.sni);
1771
1772 if (connect->alpn)
1773 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1774 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
1775 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1776#endif
1777 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
1778 conn->send_proxy_ofs = 1;
1779 conn->flags |= CO_FL_SOCKS4;
1780 }
1781 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1782 conn->send_proxy_ofs = 1;
1783 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet206368d2020-04-03 14:51:06 +02001784 }
1785
Christopher Faulet61cc8522020-04-20 14:54:42 +02001786 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1787 conn->send_proxy_ofs = 1;
1788 conn->flags |= CO_FL_SEND_PROXY;
1789 }
1790 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1791 conn->send_proxy_ofs = 1;
1792 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet206368d2020-04-03 14:51:06 +02001793 }
1794
Christopher Faulet61cc8522020-04-20 14:54:42 +02001795 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1796 /* Some servers don't like reset on close */
1797 fdtab[cs->conn->handle.fd].linger_risk = 0;
1798 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001799
Christopher Faulet61cc8522020-04-20 14:54:42 +02001800 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1801 if (xprt_add_hs(conn) < 0)
1802 status = SF_ERR_RESOURCE;
1803 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001804
Christopher Faulet61cc8522020-04-20 14:54:42 +02001805 fail_check:
1806 /* It can return one of :
1807 * - SF_ERR_NONE if everything's OK
1808 * - SF_ERR_SRVTO if there are no more servers
1809 * - SF_ERR_SRVCL if the connection was refused by the server
1810 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1811 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1812 * - SF_ERR_INTERNAL for any other purely internal errors
1813 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1814 * Note that we try to prevent the network stack from sending the ACK during the
1815 * connect() when a pure TCP check is used (without PROXY protocol).
1816 */
1817 switch (status) {
1818 case SF_ERR_NONE:
1819 /* we allow up to min(inter, timeout.connect) for a connection
1820 * to establish but only when timeout.check is set as it may be
1821 * to short for a full check otherwise
1822 */
1823 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Christopher Faulet206368d2020-04-03 14:51:06 +02001824
Christopher Faulet61cc8522020-04-20 14:54:42 +02001825 if (proxy->timeout.check && proxy->timeout.connect) {
1826 int t_con = tick_add(now_ms, proxy->timeout.connect);
1827 t->expire = tick_first(t->expire, t_con);
1828 }
1829 break;
1830 case SF_ERR_SRVTO: /* ETIMEDOUT */
1831 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1832 case SF_ERR_PRXCOND:
1833 case SF_ERR_RESOURCE:
1834 case SF_ERR_INTERNAL:
1835 chk_report_conn_err(check, errno, 0);
1836 ret = TCPCHK_EVAL_STOP;
1837 goto out;
Christopher Faulet206368d2020-04-03 14:51:06 +02001838 }
1839
Christopher Faulet61cc8522020-04-20 14:54:42 +02001840 /* don't do anything until the connection is established */
1841 if (conn->flags & CO_FL_WAIT_XPRT) {
1842 ret = TCPCHK_EVAL_WAIT;
1843 goto out;
1844 }
1845
1846 out:
1847 if (conn && check->result == CHK_RES_FAILED)
1848 conn->flags |= CO_FL_ERROR;
1849 return ret;
Christopher Faulet206368d2020-04-03 14:51:06 +02001850}
1851
Christopher Faulet61cc8522020-04-20 14:54:42 +02001852/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1853 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1854 * TCPCHK_EVAL_STOP if an error occurred.
1855 */
1856static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001857{
1858 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001859 struct tcpcheck_send *send = &rule->send;
1860 struct conn_stream *cs = check->cs;
1861 struct connection *conn = cs_conn(cs);
1862 struct buffer *tmp = NULL;
1863 struct htx *htx = NULL;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001864
Christopher Faulet61cc8522020-04-20 14:54:42 +02001865 /* reset the read & write buffer */
1866 b_reset(&check->bi);
1867 b_reset(&check->bo);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001868
Christopher Faulet61cc8522020-04-20 14:54:42 +02001869 switch (send->type) {
1870 case TCPCHK_SEND_STRING:
1871 case TCPCHK_SEND_BINARY:
1872 if (istlen(send->data) >= b_size(&check->bo)) {
1873 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1874 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1875 tcpcheck_get_step_id(check, rule));
1876 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1877 ret = TCPCHK_EVAL_STOP;
1878 goto out;
1879 }
1880 b_putist(&check->bo, send->data);
1881 break;
1882 case TCPCHK_SEND_STRING_LF:
1883 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1884 if (!b_data(&check->bo))
1885 goto out;
1886 break;
1887 case TCPCHK_SEND_BINARY_LF:
1888 tmp = alloc_trash_chunk();
1889 if (!tmp)
1890 goto error_lf;
1891 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1892 if (!b_data(tmp))
1893 goto out;
1894 tmp->area[tmp->data] = '\0';
1895 b_set_data(&check->bo, b_size(&check->bo));
1896 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
1897 goto error_lf;
1898 break;
1899 case TCPCHK_SEND_HTTP: {
1900 struct htx_sl *sl;
1901 struct ist meth, uri, vsn, clen, body;
1902 unsigned int slflags = 0;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001903
Christopher Faulet61cc8522020-04-20 14:54:42 +02001904 tmp = alloc_trash_chunk();
1905 if (!tmp)
1906 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001907
Christopher Faulet61cc8522020-04-20 14:54:42 +02001908 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1909 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1910 : http_known_methods[send->http.meth.meth]);
1911 uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
1912 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001913
Christopher Faulet61cc8522020-04-20 14:54:42 +02001914 if (istlen(vsn) == 8 &&
1915 (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1')))
1916 slflags |= HTX_SL_F_VER_11;
1917 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1918 if (!isttest(send->http.body))
1919 slflags |= HTX_SL_F_BODYLESS;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001920
Christopher Faulet61cc8522020-04-20 14:54:42 +02001921 htx = htx_from_buf(&check->bo);
1922 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1923 if (!sl)
1924 goto error_htx;
1925 sl->info.req.meth = send->http.meth.meth;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001926
Christopher Faulet61cc8522020-04-20 14:54:42 +02001927 body = send->http.body; // TODO: handle body_fmt
1928 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001929
Christopher Faulet61cc8522020-04-20 14:54:42 +02001930 if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
1931 !htx_add_header(htx, ist("Content-length"), clen))
1932 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001933
Christopher Faulet61cc8522020-04-20 14:54:42 +02001934 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1935 struct tcpcheck_http_hdr *hdr;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001936
Christopher Faulet61cc8522020-04-20 14:54:42 +02001937 list_for_each_entry(hdr, &send->http.hdrs, list) {
1938 chunk_reset(tmp);
1939 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1940 if (!b_data(tmp))
1941 continue;
1942 if (!htx_add_header(htx, hdr->name, ist2(b_orig(tmp), b_data(tmp))))
1943 goto error_htx;
1944 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001945
Christopher Faulet61cc8522020-04-20 14:54:42 +02001946 }
1947 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1948 chunk_reset(tmp);
1949 httpchk_build_status_header(check->server, tmp);
1950 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1951 goto error_htx;
1952 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001953
Christopher Faulet61cc8522020-04-20 14:54:42 +02001954 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
1955 (istlen(body) && !htx_add_data_atonce(htx, send->http.body)) ||
1956 !htx_add_endof(htx, HTX_BLK_EOM))
1957 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001958
Christopher Faulet61cc8522020-04-20 14:54:42 +02001959 htx_to_buf(htx, &check->bo);
1960 break;
1961 }
1962 case TCPCHK_SEND_UNDEF:
1963 /* Should never happen. */
1964 ret = TCPCHK_EVAL_STOP;
1965 goto out;
1966 };
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001967
Christopher Faulet6d471212020-04-22 11:09:25 +02001968
1969 if (conn->mux->snd_buf(cs, &check->bo,
1970 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Faulet815516d2020-04-21 13:02:14 +02001971 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001972 ret = TCPCHK_EVAL_STOP;
Christopher Faulet815516d2020-04-21 13:02:14 +02001973 goto out;
1974 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001975 }
Christopher Faulet6d471212020-04-22 11:09:25 +02001976 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001977 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1978 ret = TCPCHK_EVAL_WAIT;
1979 goto out;
1980 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001981
Christopher Faulet61cc8522020-04-20 14:54:42 +02001982 out:
1983 free_trash_chunk(tmp);
1984 return ret;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001985
Christopher Faulet61cc8522020-04-20 14:54:42 +02001986 error_htx:
1987 if (htx) {
1988 htx_reset(htx);
1989 htx_to_buf(htx, &check->bo);
1990 }
1991 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1992 tcpcheck_get_step_id(check, rule));
1993 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1994 ret = TCPCHK_EVAL_STOP;
1995 goto out;
1996
1997 error_lf:
1998 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1999 tcpcheck_get_step_id(check, rule));
2000 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2001 ret = TCPCHK_EVAL_STOP;
2002 goto out;
2003
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002004}
2005
Christopher Faulet61cc8522020-04-20 14:54:42 +02002006/* Try to reveice data before evaluting a tcp-check expect rule. Returns
2007 * TCPCHK_EVAL_WAIT if it is already subcribed on receive events or if nothing
2008 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
2009 * TCPCHK_EVAL_STOP if an error occurred.
2010 */
2011static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
Christopher Faulet1997eca2020-04-03 23:13:50 +02002012{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002013 struct conn_stream *cs = check->cs;
2014 struct connection *conn = cs_conn(cs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02002015 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002016 size_t max, read, cur_read = 0;
2017 int is_empty;
2018 int read_poll = MAX_READ_POLL_LOOPS;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002019
Christopher Faulet61cc8522020-04-20 14:54:42 +02002020 if (check->wait_list.events & SUB_RETRY_RECV)
2021 goto wait_more_data;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002022
Christopher Faulet61cc8522020-04-20 14:54:42 +02002023 if (cs->flags & CS_FL_EOS)
2024 goto end_recv;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002025
Christopher Faulet61cc8522020-04-20 14:54:42 +02002026 /* errors on the connection and the conn-stream were already checked */
Christopher Faulet1997eca2020-04-03 23:13:50 +02002027
Christopher Faulet61cc8522020-04-20 14:54:42 +02002028 /* prepare to detect if the mux needs more room */
2029 cs->flags &= ~CS_FL_WANT_ROOM;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002030
Christopher Faulet61cc8522020-04-20 14:54:42 +02002031 while ((cs->flags & CS_FL_RCV_MORE) ||
2032 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
2033 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
2034 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
2035 cur_read += read;
2036 if (!read ||
2037 (cs->flags & CS_FL_WANT_ROOM) ||
2038 (--read_poll <= 0) ||
2039 (read < max && read >= global.tune.recv_enough))
2040 break;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002041 }
2042
Christopher Faulet61cc8522020-04-20 14:54:42 +02002043 end_recv:
2044 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
2045 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
2046 /* Report network errors only if we got no other data. Otherwise
2047 * we'll let the upper layers decide whether the response is OK
2048 * or not. It is very common that an RST sent by the server is
2049 * reported as an error just after the last data chunk.
2050 */
2051 goto stop;
2052 }
2053 if (!cur_read) {
2054 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
2055 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2056 goto wait_more_data;
2057 }
2058 if (is_empty) {
2059 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
2060 tcpcheck_get_step_id(check, rule));
2061 if (rule->comment)
2062 chunk_appendf(&trash, " comment: '%s'", rule->comment);
2063 set_server_check_status(check, rule->expect.err_status, trash.area);
2064 goto stop;
2065 }
2066 }
Christopher Faulet1997eca2020-04-03 23:13:50 +02002067
2068 out:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002069 return ret;
2070
Christopher Faulet61cc8522020-04-20 14:54:42 +02002071 stop:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002072 ret = TCPCHK_EVAL_STOP;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002073 goto out;
2074
2075 wait_more_data:
2076 ret = TCPCHK_EVAL_WAIT;
2077 goto out;
2078}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002079
Christopher Faulet61cc8522020-04-20 14:54:42 +02002080/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
2081 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
2082 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
2083 * error occurred.
2084 */
2085static 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 +02002086{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002087 struct htx *htx = htxbuf(&check->bi);
2088 struct htx_sl *sl;
2089 struct htx_blk *blk;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002090 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002091 struct tcpcheck_expect *expect = &rule->expect;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002092 struct buffer *msg = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002093 enum healthcheck_status status;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002094 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002095 int match, inverse;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002096
Christopher Faulet61cc8522020-04-20 14:54:42 +02002097 last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
Christopher Faulet267b01b2020-04-04 10:27:09 +02002098
Christopher Faulet61cc8522020-04-20 14:54:42 +02002099 if (htx->flags & HTX_FL_PARSING_ERROR) {
2100 status = HCHK_STATUS_L7RSP;
2101 goto error;
2102 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002103
Christopher Faulet61cc8522020-04-20 14:54:42 +02002104 if (htx_is_empty(htx)) {
2105 if (last_read) {
2106 status = HCHK_STATUS_L7RSP;
2107 goto error;
2108 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002109 goto wait_more_data;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002110 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002111
Christopher Faulet61cc8522020-04-20 14:54:42 +02002112 sl = http_get_stline(htx);
2113 check->code = sl->info.res.status;
2114
2115 if (check->server &&
2116 (check->server->proxy->options & PR_O_DISABLE404) &&
2117 (check->server->next_state != SRV_ST_STOPPED) &&
2118 (check->code == 404)) {
2119 /* 404 may be accepted as "stopping" only if the server was up */
2120 goto out;
2121 }
2122
2123 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2124 /* Make GCC happy ; initialize match to a failure state. */
2125 match = inverse;
2126
2127 switch (expect->type) {
2128 case TCPCHK_EXPECT_HTTP_STATUS:
2129 match = isteq(htx_sl_res_code(sl), expect->data);
2130
2131 /* Set status and description in case of error */
2132 status = HCHK_STATUS_L7STS;
2133 desc = htx_sl_res_reason(sl);
2134 break;
2135 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
2136 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
2137
2138 /* Set status and description in case of error */
2139 status = HCHK_STATUS_L7STS;
2140 desc = htx_sl_res_reason(sl);
2141 break;
2142
2143 case TCPCHK_EXPECT_HTTP_BODY:
2144 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
2145 chunk_reset(&trash);
2146 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
2147 enum htx_blk_type type = htx_get_blk_type(blk);
2148
2149 if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
2150 break;
2151 if (type == HTX_BLK_DATA) {
2152 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
2153 break;
2154 }
2155 }
2156
2157 if (!b_data(&trash)) {
2158 if (!last_read)
2159 goto wait_more_data;
2160 status = HCHK_STATUS_L7RSP;
2161 desc = ist("HTTP content check could not find a response body");
2162 goto error;
2163 }
2164
2165 if (!last_read &&
2166 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
2167 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
2168 ret = TCPCHK_EVAL_WAIT;
2169 goto out;
2170 }
2171
2172 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002173 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002174 else
2175 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
2176
2177 /* Set status and description in case of error */
Christopher Faulet267b01b2020-04-04 10:27:09 +02002178 status = HCHK_STATUS_L7RSP;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002179 desc = (inverse
2180 ? ist("HTTP check matched unwanted content")
2181 : ist("HTTP content check did not match"));
2182 break;
2183
2184 default:
2185 /* should never happen */
2186 status = HCHK_STATUS_L7RSP;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002187 goto error;
2188 }
2189
Christopher Faulet61cc8522020-04-20 14:54:42 +02002190 /* Wait for more data on mismatch only if no minimum is defined (-1),
2191 * otherwise the absence of match is already conclusive.
2192 */
2193 if (!match && !last_read && (expect->min_recv == -1)) {
2194 ret = TCPCHK_EVAL_WAIT;
2195 goto out;
2196 }
2197
2198 if (!(match ^ inverse))
2199 goto error;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002200
2201 out:
2202 free_trash_chunk(msg);
2203 return ret;
2204
2205 error:
2206 ret = TCPCHK_EVAL_STOP;
2207 msg = alloc_trash_chunk();
2208 if (msg)
Christopher Faulet61cc8522020-04-20 14:54:42 +02002209 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002210 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2211 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002212
2213 wait_more_data:
2214 ret = TCPCHK_EVAL_WAIT;
2215 goto out;
2216}
2217
Christopher Faulet61cc8522020-04-20 14:54:42 +02002218/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
2219 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
2220 * if an error occurred.
2221 */
2222static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
2223{
2224 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2225 struct tcpcheck_expect *expect = &rule->expect;
2226 struct buffer *msg = NULL;
2227 int match, inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002228
Christopher Faulet61cc8522020-04-20 14:54:42 +02002229 last_read |= b_full(&check->bi);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002230
Christopher Faulet61cc8522020-04-20 14:54:42 +02002231 /* The current expect might need more data than the previous one, check again
2232 * that the minimum amount data required to match is respected.
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002233 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002234 if (!last_read) {
2235 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
2236 (b_data(&check->bi) < istlen(expect->data))) {
2237 ret = TCPCHK_EVAL_WAIT;
2238 goto out;
2239 }
2240 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
2241 ret = TCPCHK_EVAL_WAIT;
2242 goto out;
2243 }
2244 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002245
Christopher Faulet61cc8522020-04-20 14:54:42 +02002246 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2247 /* Make GCC happy ; initialize match to a failure state. */
2248 match = inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002249
Christopher Faulet61cc8522020-04-20 14:54:42 +02002250 switch (expect->type) {
2251 case TCPCHK_EXPECT_STRING:
2252 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02002253 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 +02002254 break;
2255 case TCPCHK_EXPECT_REGEX:
Christopher Faulet88d939c2020-04-22 15:32:11 +02002256 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 +02002257 break;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002258
Christopher Faulet61cc8522020-04-20 14:54:42 +02002259 case TCPCHK_EXPECT_REGEX_BINARY:
2260 chunk_reset(&trash);
2261 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet88d939c2020-04-22 15:32:11 +02002262 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002263 break;
2264 case TCPCHK_EXPECT_CUSTOM:
2265 if (expect->custom)
2266 ret = expect->custom(check, rule, last_read);
2267 goto out;
2268 default:
2269 /* Should never happen. */
2270 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002271 goto out;
2272 }
2273
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002274
Christopher Faulet61cc8522020-04-20 14:54:42 +02002275 /* Wait for more data on mismatch only if no minimum is defined (-1),
2276 * otherwise the absence of match is already conclusive.
2277 */
2278 if (!match && !last_read && (expect->min_recv == -1)) {
2279 ret = TCPCHK_EVAL_WAIT;
2280 goto out;
2281 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002282
Christopher Faulet61cc8522020-04-20 14:54:42 +02002283 /* Result as expected, next rule. */
2284 if (match ^ inverse)
2285 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002286
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002287
Christopher Faulet61cc8522020-04-20 14:54:42 +02002288 /* From this point on, we matched something we did not want, this is an error state. */
2289 ret = TCPCHK_EVAL_STOP;
2290 msg = alloc_trash_chunk();
2291 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002292 tcpcheck_expect_onerror_message(msg, check, rule, match, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002293 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
2294 free_trash_chunk(msg);
2295 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002296
Christopher Faulet61cc8522020-04-20 14:54:42 +02002297 out:
2298 return ret;
2299}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002300
Christopher Faulet61cc8522020-04-20 14:54:42 +02002301/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2302 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It nevers
2303 * waits.
2304 */
2305static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2306{
2307 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2308 struct act_rule *act_rule;
2309 enum act_return act_ret;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002310
Christopher Faulet61cc8522020-04-20 14:54:42 +02002311 act_rule =rule->action_kw.rule;
2312 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2313 if (act_ret != ACT_RET_CONT) {
2314 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2315 tcpcheck_get_step_id(check, rule));
2316 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2317 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002318 }
2319
Christopher Faulet61cc8522020-04-20 14:54:42 +02002320 return ret;
2321}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002322
Christopher Faulet61cc8522020-04-20 14:54:42 +02002323/* Executes a tcp-check ruleset. Note that this is called both from the
2324 * connection's wake() callback and from the check scheduling task. It returns
2325 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2326 * presenting the risk of an fd replacement.
2327 *
2328 * Please do NOT place any return statement in this function and only leave
2329 * via the out_end_tcpcheck label after setting retcode.
2330 */
2331static int tcpcheck_main(struct check *check)
2332{
2333 struct tcpcheck_rule *rule;
2334 struct conn_stream *cs = check->cs;
2335 struct connection *conn = cs_conn(cs);
2336 int must_read = 1, last_read = 0;
2337 int ret, retcode = 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002338
Christopher Faulet61cc8522020-04-20 14:54:42 +02002339 /* here, we know that the check is complete or that it failed */
2340 if (check->result != CHK_RES_UNKNOWN)
2341 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002342
Christopher Faulet61cc8522020-04-20 14:54:42 +02002343 /* 1- check for connection error, if any */
2344 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2345 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002346
Christopher Faulet61cc8522020-04-20 14:54:42 +02002347 /* 2- check if we are waiting for the connection establishment. It only
2348 * happens during TCPCHK_ACT_CONNECT. */
2349 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
2350 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
2351 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
2352 if (rule->action == TCPCHK_ACT_SEND)
2353 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2354 else if (rule->action == TCPCHK_ACT_EXPECT)
2355 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2356 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002357 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002358 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002359
2360 /* 3- check for pending outgoing data. It only happens during
2361 * TCPCHK_ACT_SEND. */
2362 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
2363 if (conn && b_data(&check->bo)) {
Christopher Faulet6d471212020-04-22 11:09:25 +02002364 ret = conn->mux->snd_buf(cs, &check->bo,
2365 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002366 if (ret <= 0) {
2367 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2368 goto out_end_tcpcheck;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002369 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002370 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002371 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2372 goto out;
2373 }
2374 }
2375 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002376 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002377
Christopher Faulet61cc8522020-04-20 14:54:42 +02002378 /* 4- check if a rule must be resume. It happens if check->current_step
2379 * is defined. */
2380 else if (check->current_step)
2381 rule = check->current_step;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002382
Christopher Faulet61cc8522020-04-20 14:54:42 +02002383 /* 5- It is the first evaluation. We must create a session and preset
2384 * tcp-check variables */
2385 else {
2386 struct tcpcheck_var *var;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002387
Christopher Faulet61cc8522020-04-20 14:54:42 +02002388 /* First evaluation, create a session */
2389 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2390 if (!check->sess) {
2391 chunk_printf(&trash, "TCPCHK error allocating check session");
2392 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2393 goto out_end_tcpcheck;
2394 }
2395 vars_init(&check->vars, SCOPE_CHECK);
2396 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Willy Tarreauef953952014-10-02 14:30:14 +02002397
Christopher Faulet61cc8522020-04-20 14:54:42 +02002398 /* Preset tcp-check variables */
2399 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2400 struct sample smp;
Willy Tarreau449f9522015-05-13 15:39:48 +02002401
Christopher Faulet61cc8522020-04-20 14:54:42 +02002402 memset(&smp, 0, sizeof(smp));
2403 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2404 smp.data = var->data;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002405 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002406 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002407 }
2408
Christopher Faulet61cc8522020-04-20 14:54:42 +02002409 /* Now evaluate the tcp-check rules */
Willy Tarreaudeccd112018-06-14 18:38:55 +02002410
Christopher Faulet61cc8522020-04-20 14:54:42 +02002411 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2412 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002413
Christopher Faulet61cc8522020-04-20 14:54:42 +02002414 check->code = 0;
2415 switch (rule->action) {
2416 case TCPCHK_ACT_CONNECT:
2417 check->current_step = rule;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002418
Christopher Faulet61cc8522020-04-20 14:54:42 +02002419 /* close but not release yet previous connection */
2420 if (check->cs) {
2421 cs_close(check->cs);
2422 retcode = -1; /* do not reuse the fd in the caller! */
2423 }
2424 eval_ret = tcpcheck_eval_connect(check, rule);
2425 must_read = 1; last_read = 0;
2426 break;
2427 case TCPCHK_ACT_SEND:
2428 check->current_step = rule;
2429 eval_ret = tcpcheck_eval_send(check, rule);
2430 must_read = 1;
2431 break;
2432 case TCPCHK_ACT_EXPECT:
2433 check->current_step = rule;
2434 if (must_read) {
2435 if (check->proxy->timeout.check)
2436 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Willy Tarreauf3d34822014-12-08 12:11:28 +01002437
Christopher Faulet61cc8522020-04-20 14:54:42 +02002438 eval_ret = tcpcheck_eval_recv(check, rule);
2439 if (eval_ret == TCPCHK_EVAL_STOP)
2440 goto out_end_tcpcheck;
2441 else if (eval_ret == TCPCHK_EVAL_WAIT)
2442 goto out;
2443 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2444 must_read = 0;
2445 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002446
Christopher Faulet61cc8522020-04-20 14:54:42 +02002447 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2448 ? tcpcheck_eval_expect_http(check, rule, last_read)
2449 : tcpcheck_eval_expect(check, rule, last_read));
Willy Tarreau00149122017-10-04 18:05:01 +02002450
Christopher Faulet61cc8522020-04-20 14:54:42 +02002451 if (eval_ret == TCPCHK_EVAL_WAIT) {
2452 check->current_step = rule->expect.head;
2453 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2454 }
2455 break;
2456 case TCPCHK_ACT_ACTION_KW:
2457 /* Don't update the current step */
2458 eval_ret = tcpcheck_eval_action_kw(check, rule);
2459 break;
2460 default:
2461 /* Otherwise, just go to the next one and don't update
2462 * the current step
2463 */
2464 eval_ret = TCPCHK_EVAL_CONTINUE;
2465 break;
2466 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002467
Christopher Faulet61cc8522020-04-20 14:54:42 +02002468 switch (eval_ret) {
2469 case TCPCHK_EVAL_CONTINUE:
2470 break;
2471 case TCPCHK_EVAL_WAIT:
2472 goto out;
2473 case TCPCHK_EVAL_STOP:
2474 goto out_end_tcpcheck;
2475 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002476 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002477
Christopher Faulet61cc8522020-04-20 14:54:42 +02002478 /* All rules was evaluated */
2479 if (check->current_step) {
2480 rule = check->current_step;
Willy Tarreau00149122017-10-04 18:05:01 +02002481
Christopher Faulet61cc8522020-04-20 14:54:42 +02002482 if (rule->action == TCPCHK_ACT_EXPECT) {
2483 struct buffer *msg;
Willy Tarreau00149122017-10-04 18:05:01 +02002484
Christopher Faulet61cc8522020-04-20 14:54:42 +02002485 if (check->server &&
2486 (check->server->proxy->options & PR_O_DISABLE404) &&
2487 (check->server->next_state != SRV_ST_STOPPED) &&
2488 (check->code == 404)) {
2489 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2490 goto out_end_tcpcheck;
2491 }
Christopher Fauletd7e63962020-04-17 20:15:59 +02002492
Christopher Faulet61cc8522020-04-20 14:54:42 +02002493 msg = alloc_trash_chunk();
2494 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002495 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002496 set_server_check_status(check, rule->expect.ok_status,
2497 (msg ? b_head(msg) : "(tcp-check)"));
2498 free_trash_chunk(msg);
2499 }
2500 else if (rule->action == TCPCHK_ACT_CONNECT) {
2501 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2502 enum healthcheck_status status = ((conn && ssl_sock_is_ssl(conn)) ? HCHK_STATUS_L6OK : HCHK_STATUS_L4OK);
Olivier Houchardff1e9f32019-09-20 17:18:35 +02002503
Christopher Faulet61cc8522020-04-20 14:54:42 +02002504 set_server_check_status(check, status, msg);
2505 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002506 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002507 else
2508 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet14cd3162020-04-16 14:50:06 +02002509
Christopher Faulet61cc8522020-04-20 14:54:42 +02002510 out_end_tcpcheck:
2511 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2512 chk_report_conn_err(check, errno, 0);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002513
Christopher Faulet61cc8522020-04-20 14:54:42 +02002514 /* cleanup before leaving */
2515 check->current_step = NULL;
2516 if (check->sess != NULL) {
2517 vars_prune(&check->vars, check->sess, NULL);
2518 session_free(check->sess);
2519 check->sess = NULL;
2520 }
2521 out:
2522 return retcode;
2523}
Christopher Fauletbb591a12020-04-01 16:52:17 +02002524
Christopher Faulet14cd3162020-04-16 14:50:06 +02002525
Christopher Faulet61cc8522020-04-20 14:54:42 +02002526/**************************************************************************/
2527/************** Health-checks based on an external process ****************/
2528/**************************************************************************/
2529static struct list pid_list = LIST_HEAD_INIT(pid_list);
2530static struct pool_head *pool_head_pid_list;
2531__decl_spinlock(pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002532
Christopher Faulet61cc8522020-04-20 14:54:42 +02002533struct extcheck_env {
2534 char *name; /* environment variable name */
2535 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
2536};
Christopher Faulet14cd3162020-04-16 14:50:06 +02002537
Christopher Faulet61cc8522020-04-20 14:54:42 +02002538/* environment variables memory requirement for different types of data */
2539#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
2540 * such environment variables are not updatable. */
2541#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
2542#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
2543#define EXTCHK_SIZE_ADDR INET6_ADDRSTRLEN+1 /* max string length for an address */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002544
Christopher Faulet61cc8522020-04-20 14:54:42 +02002545/* external checks environment variables */
2546enum {
2547 EXTCHK_PATH = 0,
Willy Tarreauca79f592019-07-17 19:04:47 +02002548
Christopher Faulet61cc8522020-04-20 14:54:42 +02002549 /* Proxy specific environment variables */
2550 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
2551 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
2552 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
2553 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002554
Christopher Faulet61cc8522020-04-20 14:54:42 +02002555 /* Server specific environment variables */
2556 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
2557 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
2558 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
2559 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
2560 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
2561 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002562
Christopher Faulet61cc8522020-04-20 14:54:42 +02002563 EXTCHK_SIZE
2564};
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002565
Christopher Faulet61cc8522020-04-20 14:54:42 +02002566const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
2567 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
2568 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
2569 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
2570 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
2571 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
2572 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
2573 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
2574 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
2575 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
2576 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
2577 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
2578};
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002579
Christopher Faulet61cc8522020-04-20 14:54:42 +02002580void block_sigchld(void)
2581{
2582 sigset_t set;
2583 sigemptyset(&set);
2584 sigaddset(&set, SIGCHLD);
2585 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
2586}
Willy Tarreaube373152018-09-06 11:45:30 +02002587
Christopher Faulet61cc8522020-04-20 14:54:42 +02002588void unblock_sigchld(void)
2589{
2590 sigset_t set;
2591 sigemptyset(&set);
2592 sigaddset(&set, SIGCHLD);
2593 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002594}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002595
Christopher Faulet61cc8522020-04-20 14:54:42 +02002596static struct pid_list *pid_list_add(pid_t pid, struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002597{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002598 struct pid_list *elem;
2599 struct check *check = t->context;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002600
Christopher Faulet61cc8522020-04-20 14:54:42 +02002601 elem = pool_alloc(pool_head_pid_list);
2602 if (!elem)
2603 return NULL;
2604 elem->pid = pid;
2605 elem->t = t;
2606 elem->exited = 0;
2607 check->curpid = elem;
2608 LIST_INIT(&elem->list);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002609
Christopher Faulet61cc8522020-04-20 14:54:42 +02002610 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2611 LIST_ADD(&pid_list, &elem->list);
2612 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002613
Christopher Faulet61cc8522020-04-20 14:54:42 +02002614 return elem;
2615}
Christopher Faulete5870d82020-04-15 11:32:03 +02002616
Christopher Faulet61cc8522020-04-20 14:54:42 +02002617static void pid_list_del(struct pid_list *elem)
2618{
2619 struct check *check;
Christopher Faulete5870d82020-04-15 11:32:03 +02002620
Christopher Faulet61cc8522020-04-20 14:54:42 +02002621 if (!elem)
2622 return;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002623
Christopher Faulet61cc8522020-04-20 14:54:42 +02002624 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2625 LIST_DEL(&elem->list);
2626 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002627
Christopher Faulet61cc8522020-04-20 14:54:42 +02002628 if (!elem->exited)
2629 kill(elem->pid, SIGTERM);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002630
Christopher Faulet61cc8522020-04-20 14:54:42 +02002631 check = elem->t->context;
2632 check->curpid = NULL;
2633 pool_free(pool_head_pid_list, elem);
2634}
Christopher Faulet14cd3162020-04-16 14:50:06 +02002635
Christopher Faulet61cc8522020-04-20 14:54:42 +02002636/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
2637static void pid_list_expire(pid_t pid, int status)
2638{
2639 struct pid_list *elem;
Christopher Faulete5870d82020-04-15 11:32:03 +02002640
Christopher Faulet61cc8522020-04-20 14:54:42 +02002641 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2642 list_for_each_entry(elem, &pid_list, list) {
2643 if (elem->pid == pid) {
2644 elem->t->expire = now_ms;
2645 elem->status = status;
2646 elem->exited = 1;
2647 task_wakeup(elem->t, TASK_WOKEN_IO);
2648 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002649 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002650 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002651 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
2652}
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002653
Christopher Faulet61cc8522020-04-20 14:54:42 +02002654static void sigchld_handler(struct sig_handler *sh)
2655{
2656 pid_t pid;
2657 int status;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002658
Christopher Faulet61cc8522020-04-20 14:54:42 +02002659 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
2660 pid_list_expire(pid, status);
2661}
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002662
Christopher Faulet61cc8522020-04-20 14:54:42 +02002663static int init_pid_list(void)
2664{
2665 if (pool_head_pid_list != NULL)
2666 /* Nothing to do */
2667 return 0;
2668
2669 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
2670 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
2671 strerror(errno));
2672 return 1;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002673 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002674
Christopher Faulet61cc8522020-04-20 14:54:42 +02002675 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
2676 if (pool_head_pid_list == NULL) {
2677 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
2678 strerror(errno));
2679 return 1;
2680 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002681
Christopher Faulet61cc8522020-04-20 14:54:42 +02002682 return 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02002683}
2684
Christopher Faulet61cc8522020-04-20 14:54:42 +02002685/* helper macro to set an environment variable and jump to a specific label on failure. */
2686#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002687
Christopher Faulet61cc8522020-04-20 14:54:42 +02002688/*
2689 * helper function to allocate enough memory to store an environment variable.
2690 * It will also check that the environment variable is updatable, and silently
2691 * fail if not.
2692 */
2693static int extchk_setenv(struct check *check, int idx, const char *value)
2694{
2695 int len, ret;
2696 char *envname;
2697 int vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002698
Christopher Faulet61cc8522020-04-20 14:54:42 +02002699 if (idx < 0 || idx >= EXTCHK_SIZE) {
2700 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
2701 return 1;
2702 }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002703
Christopher Faulet61cc8522020-04-20 14:54:42 +02002704 envname = extcheck_envs[idx].name;
2705 vmaxlen = extcheck_envs[idx].vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002706
Christopher Faulet61cc8522020-04-20 14:54:42 +02002707 /* Check if the environment variable is already set, and silently reject
2708 * the update if this one is not updatable. */
2709 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
2710 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002711
Christopher Faulet61cc8522020-04-20 14:54:42 +02002712 /* Instead of sending NOT_USED, sending an empty value is preferable */
2713 if (strcmp(value, "NOT_USED") == 0) {
2714 value = "";
Christopher Fauletf9585d82020-04-16 13:25:58 +02002715 }
2716
Christopher Faulet61cc8522020-04-20 14:54:42 +02002717 len = strlen(envname) + 1;
2718 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
2719 len += strlen(value);
2720 else
2721 len += vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002722
Christopher Faulet61cc8522020-04-20 14:54:42 +02002723 if (!check->envp[idx])
2724 check->envp[idx] = malloc(len + 1);
Christopher Fauletf9585d82020-04-16 13:25:58 +02002725
Christopher Faulet61cc8522020-04-20 14:54:42 +02002726 if (!check->envp[idx]) {
2727 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
2728 return 1;
2729 }
2730 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
2731 if (ret < 0) {
2732 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
2733 return 1;
2734 }
2735 else if (ret > len) {
2736 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
2737 return 1;
2738 }
2739 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002740}
2741
Christopher Faulet61cc8522020-04-20 14:54:42 +02002742static int prepare_external_check(struct check *check)
Christopher Faulete5870d82020-04-15 11:32:03 +02002743{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002744 struct server *s = check->server;
2745 struct proxy *px = s->proxy;
2746 struct listener *listener = NULL, *l;
2747 int i;
2748 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
2749 char buf[256];
Christopher Faulete5870d82020-04-15 11:32:03 +02002750
Christopher Faulet61cc8522020-04-20 14:54:42 +02002751 list_for_each_entry(l, &px->conf.listeners, by_fe)
2752 /* Use the first INET, INET6 or UNIX listener */
2753 if (l->addr.ss_family == AF_INET ||
2754 l->addr.ss_family == AF_INET6 ||
2755 l->addr.ss_family == AF_UNIX) {
2756 listener = l;
2757 break;
2758 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002759
Christopher Faulet61cc8522020-04-20 14:54:42 +02002760 check->curpid = NULL;
2761 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
2762 if (!check->envp) {
2763 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
2764 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002765 }
2766
Christopher Faulet61cc8522020-04-20 14:54:42 +02002767 check->argv = calloc(6, sizeof(char *));
2768 if (!check->argv) {
2769 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2770 goto err;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002771 }
2772
Christopher Faulet61cc8522020-04-20 14:54:42 +02002773 check->argv[0] = px->check_command;
Christopher Faulete5870d82020-04-15 11:32:03 +02002774
Christopher Faulet61cc8522020-04-20 14:54:42 +02002775 if (!listener) {
2776 check->argv[1] = strdup("NOT_USED");
2777 check->argv[2] = strdup("NOT_USED");
Christopher Faulete5870d82020-04-15 11:32:03 +02002778 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002779 else if (listener->addr.ss_family == AF_INET ||
2780 listener->addr.ss_family == AF_INET6) {
2781 addr_to_str(&listener->addr, buf, sizeof(buf));
2782 check->argv[1] = strdup(buf);
2783 port_to_str(&listener->addr, buf, sizeof(buf));
2784 check->argv[2] = strdup(buf);
2785 }
2786 else if (listener->addr.ss_family == AF_UNIX) {
2787 const struct sockaddr_un *un;
Christopher Faulete5870d82020-04-15 11:32:03 +02002788
Christopher Faulet61cc8522020-04-20 14:54:42 +02002789 un = (struct sockaddr_un *)&listener->addr;
2790 check->argv[1] = strdup(un->sun_path);
2791 check->argv[2] = strdup("NOT_USED");
2792 }
2793 else {
2794 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
2795 goto err;
2796 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002797
Christopher Faulet61cc8522020-04-20 14:54:42 +02002798 if (!check->argv[1] || !check->argv[2]) {
2799 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2800 goto err;
2801 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002802
Christopher Faulet61cc8522020-04-20 14:54:42 +02002803 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
2804 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
2805 if (!check->argv[3] || !check->argv[4]) {
2806 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2807 goto err;
2808 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002809
Christopher Faulet61cc8522020-04-20 14:54:42 +02002810 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2811 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2812 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Christopher Faulete5870d82020-04-15 11:32:03 +02002813
Christopher Faulet61cc8522020-04-20 14:54:42 +02002814 for (i = 0; i < 5; i++) {
2815 if (!check->argv[i]) {
2816 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2817 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002818 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002819 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002820
Christopher Faulet61cc8522020-04-20 14:54:42 +02002821 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
2822 /* Add proxy environment variables */
2823 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
2824 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
2825 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
2826 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
2827 /* Add server environment variables */
2828 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
2829 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
2830 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
2831 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
2832 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
2833 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002834
Christopher Faulet61cc8522020-04-20 14:54:42 +02002835 /* Ensure that we don't leave any hole in check->envp */
2836 for (i = 0; i < EXTCHK_SIZE; i++)
2837 if (!check->envp[i])
2838 EXTCHK_SETENV(check, i, "", err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002839
Christopher Faulet61cc8522020-04-20 14:54:42 +02002840 return 1;
2841err:
2842 if (check->envp) {
2843 for (i = 0; i < EXTCHK_SIZE; i++)
2844 free(check->envp[i]);
2845 free(check->envp);
2846 check->envp = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002847 }
2848
Christopher Faulet61cc8522020-04-20 14:54:42 +02002849 if (check->argv) {
2850 for (i = 1; i < 5; i++)
2851 free(check->argv[i]);
2852 free(check->argv);
2853 check->argv = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002854 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002855 return 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002856}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01002857
Christopher Faulet61cc8522020-04-20 14:54:42 +02002858/*
2859 * establish a server health-check that makes use of a process.
2860 *
2861 * It can return one of :
2862 * - SF_ERR_NONE if everything's OK
2863 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2864 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2865 *
2866 * Blocks and then unblocks SIGCHLD
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002867 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002868static int connect_proc_chk(struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002869{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002870 char buf[256];
2871 struct check *check = t->context;
2872 struct server *s = check->server;
2873 struct proxy *px = s->proxy;
2874 int status;
2875 pid_t pid;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002876
Christopher Faulet61cc8522020-04-20 14:54:42 +02002877 status = SF_ERR_RESOURCE;
Willy Tarreauf2c87352015-05-13 12:08:21 +02002878
Christopher Faulet61cc8522020-04-20 14:54:42 +02002879 block_sigchld();
Willy Tarreau7df8ca62019-07-15 10:57:51 +02002880
Christopher Faulet61cc8522020-04-20 14:54:42 +02002881 pid = fork();
2882 if (pid < 0) {
2883 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
2884 (global.tune.options & GTUNE_INSECURE_FORK) ?
2885 "" : " (likely caused by missing 'insecure-fork-wanted')",
2886 strerror(errno));
2887 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002888 goto out;
2889 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002890 if (pid == 0) {
2891 /* Child */
2892 extern char **environ;
2893 struct rlimit limit;
2894 int fd;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01002895
Christopher Faulet61cc8522020-04-20 14:54:42 +02002896 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
2897 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002898
Christopher Faulet61cc8522020-04-20 14:54:42 +02002899 my_closefrom(fd);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002900
Christopher Faulet61cc8522020-04-20 14:54:42 +02002901 /* restore the initial FD limits */
2902 limit.rlim_cur = rlim_fd_cur_at_boot;
2903 limit.rlim_max = rlim_fd_max_at_boot;
2904 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
2905 getrlimit(RLIMIT_NOFILE, &limit);
2906 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
2907 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
2908 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
2909 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002910
Christopher Faulet61cc8522020-04-20 14:54:42 +02002911 environ = check->envp;
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002912
Christopher Faulet61cc8522020-04-20 14:54:42 +02002913 /* Update some environment variables and command args: curconn, server addr and server port */
2914 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002915
Christopher Faulet61cc8522020-04-20 14:54:42 +02002916 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2917 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002918
Christopher Faulet61cc8522020-04-20 14:54:42 +02002919 *check->argv[4] = 0;
2920 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2921 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
2922 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002923
Christopher Faulet61cc8522020-04-20 14:54:42 +02002924 haproxy_unblock_signals();
2925 execvp(px->check_command, check->argv);
2926 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
2927 strerror(errno));
2928 exit(-1);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002929 }
2930
Christopher Faulet61cc8522020-04-20 14:54:42 +02002931 /* Parent */
2932 if (check->result == CHK_RES_UNKNOWN) {
2933 if (pid_list_add(pid, t) != NULL) {
2934 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
2935
2936 if (px->timeout.check && px->timeout.connect) {
2937 int t_con = tick_add(now_ms, px->timeout.connect);
2938 t->expire = tick_first(t->expire, t_con);
2939 }
2940 status = SF_ERR_NONE;
2941 goto out;
2942 }
2943 else {
2944 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2945 }
2946 kill(pid, SIGTERM); /* process creation error */
2947 }
2948 else
2949 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2950
2951out:
2952 unblock_sigchld();
2953 return status;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002954}
2955
Christopher Faulet61cc8522020-04-20 14:54:42 +02002956/*
2957 * manages a server health-check that uses an external process. Returns
2958 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002959 *
2960 * Please do NOT place any return statement in this function and only leave
Christopher Faulet61cc8522020-04-20 14:54:42 +02002961 * via the out_unlock label.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002962 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002963static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002964{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002965 struct check *check = context;
2966 struct server *s = check->server;
2967 int rv;
2968 int ret;
2969 int expired = tick_is_expired(t->expire, now_ms);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002970
Christopher Faulet61cc8522020-04-20 14:54:42 +02002971 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
2972 if (!(check->state & CHK_ST_INPROGRESS)) {
2973 /* no check currently running */
2974 if (!expired) /* woke up too early */
2975 goto out_unlock;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002976
Christopher Faulet61cc8522020-04-20 14:54:42 +02002977 /* we don't send any health-checks when the proxy is
2978 * stopped, the server should not be checked or the check
2979 * is disabled.
2980 */
2981 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
2982 s->proxy->state == PR_STSTOPPED)
2983 goto reschedule;
Gaetan Rivetb616add2020-02-07 15:37:17 +01002984
Christopher Faulet61cc8522020-04-20 14:54:42 +02002985 /* we'll initiate a new check */
2986 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulet370e0f12020-04-16 09:52:42 +02002987
Christopher Faulet61cc8522020-04-20 14:54:42 +02002988 check->state |= CHK_ST_INPROGRESS;
2989
2990 ret = connect_proc_chk(t);
2991 if (ret == SF_ERR_NONE) {
2992 /* the process was forked, we allow up to min(inter,
2993 * timeout.connect) for it to report its status, but
2994 * only when timeout.check is set as it may be to short
2995 * for a full check otherwise.
2996 */
2997 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
2998
2999 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
3000 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
3001 t->expire = tick_first(t->expire, t_con);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003002 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003003 task_set_affinity(t, tid_bit);
3004 goto reschedule;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003005 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003006
Christopher Faulet61cc8522020-04-20 14:54:42 +02003007 /* here, we failed to start the check */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003008
Christopher Faulet61cc8522020-04-20 14:54:42 +02003009 check->state &= ~CHK_ST_INPROGRESS;
3010 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003011
Christopher Faulet61cc8522020-04-20 14:54:42 +02003012 /* we allow up to min(inter, timeout.connect) for a connection
3013 * to establish but only when timeout.check is set
3014 * as it may be to short for a full check otherwise
3015 */
3016 while (tick_is_expired(t->expire, now_ms)) {
3017 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003018
Christopher Faulet61cc8522020-04-20 14:54:42 +02003019 t_con = tick_add(t->expire, s->proxy->timeout.connect);
3020 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003021
Christopher Faulet61cc8522020-04-20 14:54:42 +02003022 if (s->proxy->timeout.check)
3023 t->expire = tick_first(t->expire, t_con);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003024 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003025 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003026 else {
3027 /* there was a test running.
3028 * First, let's check whether there was an uncaught error,
3029 * which can happen on connect timeout or error.
3030 */
3031 if (check->result == CHK_RES_UNKNOWN) {
3032 /* good connection is enough for pure TCP check */
3033 struct pid_list *elem = check->curpid;
3034 int status = HCHK_STATUS_UNKNOWN;
Christopher Faulet370e0f12020-04-16 09:52:42 +02003035
Christopher Faulet61cc8522020-04-20 14:54:42 +02003036 if (elem->exited) {
3037 status = elem->status; /* Save in case the process exits between use below */
3038 if (!WIFEXITED(status))
3039 check->code = -1;
3040 else
3041 check->code = WEXITSTATUS(status);
3042 if (!WIFEXITED(status) || WEXITSTATUS(status))
3043 status = HCHK_STATUS_PROCERR;
3044 else
3045 status = HCHK_STATUS_PROCOK;
3046 } else if (expired) {
3047 status = HCHK_STATUS_PROCTOUT;
3048 ha_warning("kill %d\n", (int)elem->pid);
3049 kill(elem->pid, SIGTERM);
3050 }
3051 set_server_check_status(check, status, NULL);
3052 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003053
Christopher Faulet61cc8522020-04-20 14:54:42 +02003054 if (check->result == CHK_RES_FAILED) {
3055 /* a failure or timeout detected */
3056 check_notify_failure(check);
3057 }
3058 else if (check->result == CHK_RES_CONDPASS) {
3059 /* check is OK but asks for stopping mode */
3060 check_notify_stopping(check);
3061 }
3062 else if (check->result == CHK_RES_PASSED) {
3063 /* a success was detected */
3064 check_notify_success(check);
3065 }
3066 task_set_affinity(t, 1);
3067 check->state &= ~CHK_ST_INPROGRESS;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003068
Christopher Faulet61cc8522020-04-20 14:54:42 +02003069 pid_list_del(check->curpid);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003070
Christopher Faulet61cc8522020-04-20 14:54:42 +02003071 rv = 0;
3072 if (global.spread_checks > 0) {
3073 rv = srv_getinter(check) * global.spread_checks / 100;
3074 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3075 }
3076 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
3077 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003078
Christopher Faulet61cc8522020-04-20 14:54:42 +02003079 reschedule:
3080 while (tick_is_expired(t->expire, now_ms))
3081 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulete5870d82020-04-15 11:32:03 +02003082
Christopher Faulet61cc8522020-04-20 14:54:42 +02003083 out_unlock:
3084 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3085 return t;
3086}
Baptiste Assmann248f1172018-03-01 21:49:01 +01003087
Baptiste Assmann248f1172018-03-01 21:49:01 +01003088
Christopher Faulet61cc8522020-04-20 14:54:42 +02003089/**************************************************************************/
3090/***************** Health-checks based on connections *********************/
3091/**************************************************************************/
3092/* This function is used only for server health-checks. It handles connection
3093 * status updates including errors. If necessary, it wakes the check task up.
3094 * It returns 0 on normal cases, <0 if at least one close() has happened on the
3095 * connection (eg: reconnect). It relies on tcpcheck_main().
3096 */
3097static int wake_srv_chk(struct conn_stream *cs)
3098{
3099 struct connection *conn = cs->conn;
3100 struct check *check = cs->data;
3101 struct email_alertq *q = container_of(check, typeof(*q), check);
3102 int ret = 0;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003103
Christopher Faulet61cc8522020-04-20 14:54:42 +02003104 if (check->server)
3105 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3106 else
3107 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulete5870d82020-04-15 11:32:03 +02003108
Christopher Faulet61cc8522020-04-20 14:54:42 +02003109 /* we may have to make progress on the TCP checks */
3110 ret = tcpcheck_main(check);
Christopher Fauletdf38f882020-04-07 16:04:38 +02003111
Christopher Faulet61cc8522020-04-20 14:54:42 +02003112 cs = check->cs;
3113 conn = cs->conn;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003114
Christopher Faulet61cc8522020-04-20 14:54:42 +02003115 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
3116 /* We may get error reports bypassing the I/O handlers, typically
3117 * the case when sending a pure TCP check which fails, then the I/O
3118 * handlers above are not called. This is completely handled by the
3119 * main processing task so let's simply wake it up. If we get here,
3120 * we expect errno to still be valid.
3121 */
3122 chk_report_conn_err(check, errno, 0);
3123 task_wakeup(check->task, TASK_WOKEN_IO);
3124 }
3125
3126 if (check->result != CHK_RES_UNKNOWN) {
3127 /* Check complete or aborted. If connection not yet closed do it
3128 * now and wake the check task up to be sure the result is
3129 * handled ASAP. */
3130 conn_sock_drain(conn);
3131 cs_close(cs);
3132 ret = -1;
3133 /* We may have been scheduled to run, and the
3134 * I/O handler expects to have a cs, so remove
3135 * the tasklet
3136 */
3137 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3138 task_wakeup(check->task, TASK_WOKEN_IO);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003139 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003140
3141 if (check->server)
3142 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Christopher Fauletec07e382020-04-07 14:56:26 +02003143 else
Christopher Faulet61cc8522020-04-20 14:54:42 +02003144 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003145
Christopher Faulet61cc8522020-04-20 14:54:42 +02003146 /* if a connection got replaced, we must absolutely prevent the connection
3147 * handler from touching its fd, and perform the FD polling updates ourselves
3148 */
3149 if (ret < 0)
3150 conn_cond_update_polling(conn);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003151
Christopher Faulet61cc8522020-04-20 14:54:42 +02003152 return ret;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003153}
3154
Christopher Faulet61cc8522020-04-20 14:54:42 +02003155/* This function checks if any I/O is wanted, and if so, attempts to do so */
3156static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Simon Hormanb1900d52015-01-30 11:22:54 +09003157{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003158 struct check *check = ctx;
3159 struct conn_stream *cs = check->cs;
3160 struct email_alertq *q = container_of(check, typeof(*q), check);
3161 int ret = 0;
Simon Hormanb1900d52015-01-30 11:22:54 +09003162
Christopher Faulet61cc8522020-04-20 14:54:42 +02003163 if (!(check->wait_list.events & SUB_RETRY_SEND))
3164 ret = wake_srv_chk(cs);
3165 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
3166 if (check->server)
3167 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3168 else
3169 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Hormanb1900d52015-01-30 11:22:54 +09003170
Christopher Faulet61cc8522020-04-20 14:54:42 +02003171 if (unlikely(check->result == CHK_RES_FAILED)) {
3172 /* collect possible new errors */
3173 if (cs->conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
3174 chk_report_conn_err(check, 0, 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003175
Christopher Faulet61cc8522020-04-20 14:54:42 +02003176 /* Reset the check buffer... */
3177 b_reset(&check->bi);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003178
Christopher Faulet61cc8522020-04-20 14:54:42 +02003179 /* Close the connection... We still attempt to nicely close if,
3180 * for instance, SSL needs to send a "close notify." Later, we perform
3181 * a hard close and reset the connection if some data are pending,
3182 * otherwise we end up with many TIME_WAITs and eat all the source port
3183 * range quickly. To avoid sending RSTs all the time, we first try to
3184 * drain pending data.
3185 */
3186 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
3187 * connection, to make sure cs_shutw() will not lead to a shutdown()
3188 * that would provoke TIME_WAITs.
3189 */
3190 cs_shutr(cs, CS_SHR_DRAIN);
3191 cs_shutw(cs, CS_SHW_NORMAL);
Simon Hormanb1900d52015-01-30 11:22:54 +09003192
Christopher Faulet61cc8522020-04-20 14:54:42 +02003193 /* OK, let's not stay here forever */
3194 if (check->result == CHK_RES_FAILED)
3195 cs->conn->flags |= CO_FL_ERROR;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003196
Christopher Faulet61cc8522020-04-20 14:54:42 +02003197 task_wakeup(t, TASK_WOKEN_IO);
3198 }
3199
3200 if (check->server)
3201 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3202 else
3203 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003204 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003205 return NULL;
Simon Hormanbfb5d332015-01-30 11:22:55 +09003206}
3207
Christopher Faulet61cc8522020-04-20 14:54:42 +02003208/* manages a server health-check that uses a connection. Returns
3209 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
3210 *
3211 * Please do NOT place any return statement in this function and only leave
3212 * via the out_unlock label.
3213 */
3214static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003215{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003216 struct check *check = context;
3217 struct proxy *proxy = check->proxy;
3218 struct conn_stream *cs = check->cs;
3219 struct connection *conn = cs_conn(cs);
3220 int rv;
3221 int expired = tick_is_expired(t->expire, now_ms);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003222
Christopher Faulet61cc8522020-04-20 14:54:42 +02003223 if (check->server)
3224 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3225 if (!(check->state & CHK_ST_INPROGRESS)) {
3226 /* no check currently running */
3227 if (!expired) /* woke up too early */
3228 goto out_unlock;
Christopher Faulete5870d82020-04-15 11:32:03 +02003229
Christopher Faulet61cc8522020-04-20 14:54:42 +02003230 /* we don't send any health-checks when the proxy is
3231 * stopped, the server should not be checked or the check
3232 * is disabled.
3233 */
3234 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3235 proxy->state == PR_STSTOPPED)
3236 goto reschedule;
Christopher Faulete5870d82020-04-15 11:32:03 +02003237
Christopher Faulet61cc8522020-04-20 14:54:42 +02003238 /* we'll initiate a new check */
3239 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulete5870d82020-04-15 11:32:03 +02003240
Christopher Faulet61cc8522020-04-20 14:54:42 +02003241 check->state |= CHK_ST_INPROGRESS;
3242 b_reset(&check->bi);
3243 b_reset(&check->bo);
Christopher Faulete5870d82020-04-15 11:32:03 +02003244
Christopher Faulet61cc8522020-04-20 14:54:42 +02003245 task_set_affinity(t, tid_bit);
3246 cs = check->cs;
3247 conn = cs_conn(cs);
3248 if (!conn) {
3249 check->current_step = NULL;
3250 tcpcheck_main(check);
3251 goto out_unlock;
3252 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003253
Christopher Faulet61cc8522020-04-20 14:54:42 +02003254 conn->flags |= CO_FL_ERROR;
3255 chk_report_conn_err(check, 0, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003256
Christopher Faulet61cc8522020-04-20 14:54:42 +02003257 /* here, we have seen a synchronous error, no fd was allocated */
3258 task_set_affinity(t, MAX_THREADS_MASK);
3259 if (cs) {
3260 if (check->wait_list.events)
3261 cs->conn->xprt->unsubscribe(cs->conn,
3262 cs->conn->xprt_ctx,
3263 check->wait_list.events,
3264 &check->wait_list);
3265 /* We may have been scheduled to run, and the
3266 * I/O handler expects to have a cs, so remove
3267 * the tasklet
3268 */
3269 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3270 cs_destroy(cs);
3271 cs = check->cs = NULL;
3272 conn = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003273 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003274
Christopher Faulet61cc8522020-04-20 14:54:42 +02003275 check->state &= ~CHK_ST_INPROGRESS;
3276 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003277
Christopher Faulet61cc8522020-04-20 14:54:42 +02003278 /* we allow up to min(inter, timeout.connect) for a connection
3279 * to establish but only when timeout.check is set
3280 * as it may be to short for a full check otherwise
3281 */
3282 while (tick_is_expired(t->expire, now_ms)) {
3283 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003284
Christopher Faulet61cc8522020-04-20 14:54:42 +02003285 t_con = tick_add(t->expire, proxy->timeout.connect);
3286 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3287 if (proxy->timeout.check)
3288 t->expire = tick_first(t->expire, t_con);
3289 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003290 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003291 else {
3292 /* there was a test running.
3293 * First, let's check whether there was an uncaught error,
3294 * which can happen on connect timeout or error.
3295 */
3296 if (check->result == CHK_RES_UNKNOWN) {
3297 if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
3298 chk_report_conn_err(check, 0, expired);
3299 }
3300 else
3301 goto out_unlock; /* timeout not reached, wait again */
3302 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003303
Christopher Faulet61cc8522020-04-20 14:54:42 +02003304 /* check complete or aborted */
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003305
Christopher Faulet61cc8522020-04-20 14:54:42 +02003306 check->current_step = NULL;
3307 if (check->sess != NULL) {
3308 session_free(check->sess);
3309 check->sess = NULL;
3310 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003311
Christopher Faulet61cc8522020-04-20 14:54:42 +02003312 if (conn && conn->xprt) {
3313 /* The check was aborted and the connection was not yet closed.
3314 * This can happen upon timeout, or when an external event such
3315 * as a failed response coupled with "observe layer7" caused the
3316 * server state to be suddenly changed.
3317 */
3318 conn_sock_drain(conn);
3319 cs_close(cs);
3320 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003321
Christopher Faulet61cc8522020-04-20 14:54:42 +02003322 if (cs) {
3323 if (check->wait_list.events)
3324 cs->conn->xprt->unsubscribe(cs->conn,
3325 cs->conn->xprt_ctx,
3326 check->wait_list.events,
3327 &check->wait_list);
3328 /* We may have been scheduled to run, and the
3329 * I/O handler expects to have a cs, so remove
3330 * the tasklet
3331 */
3332 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3333 cs_destroy(cs);
3334 cs = check->cs = NULL;
3335 conn = NULL;
3336 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003337
Christopher Faulet61cc8522020-04-20 14:54:42 +02003338 if (check->server) {
3339 if (check->result == CHK_RES_FAILED) {
3340 /* a failure or timeout detected */
3341 check_notify_failure(check);
3342 }
3343 else if (check->result == CHK_RES_CONDPASS) {
3344 /* check is OK but asks for stopping mode */
3345 check_notify_stopping(check);
3346 }
3347 else if (check->result == CHK_RES_PASSED) {
3348 /* a success was detected */
3349 check_notify_success(check);
3350 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003351 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003352 task_set_affinity(t, MAX_THREADS_MASK);
3353 check->state &= ~CHK_ST_INPROGRESS;
3354
3355 if (check->server) {
3356 rv = 0;
3357 if (global.spread_checks > 0) {
3358 rv = srv_getinter(check) * global.spread_checks / 100;
3359 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3360 }
3361 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003362 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003363 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003364
Christopher Faulet61cc8522020-04-20 14:54:42 +02003365 reschedule:
3366 while (tick_is_expired(t->expire, now_ms))
3367 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3368 out_unlock:
3369 if (check->server)
3370 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3371 return t;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003372}
3373
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003374
Christopher Faulet61cc8522020-04-20 14:54:42 +02003375/**************************************************************************/
3376/******************* Internals to parse tcp-check rules *******************/
3377/**************************************************************************/
3378struct action_kw_list tcp_check_keywords = {
3379 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
3380};
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003381
Christopher Faulet61cc8522020-04-20 14:54:42 +02003382/* Return the struct action_kw associated to a keyword */
3383static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003384{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003385 return action_lookup(&tcp_check_keywords.list, kw);
3386}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003387
Christopher Faulet61cc8522020-04-20 14:54:42 +02003388static void action_kw_tcp_check_build_list(struct buffer *chk)
3389{
3390 action_build_list(&tcp_check_keywords.list, chk);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003391}
3392
Christopher Faulet61cc8522020-04-20 14:54:42 +02003393/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
3394 * returned on error.
3395 */
3396static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
3397 struct list *rules, struct action_kw *kw,
3398 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003399{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003400 struct tcpcheck_rule *chk = NULL;
3401 struct act_rule *actrule = NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003402
Christopher Faulet61cc8522020-04-20 14:54:42 +02003403 actrule = calloc(1, sizeof(*actrule));
3404 if (!actrule) {
3405 memprintf(errmsg, "out of memory");
3406 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003407 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003408 actrule->kw = kw;
3409 actrule->from = ACT_F_TCP_CHK;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003410
Christopher Faulet61cc8522020-04-20 14:54:42 +02003411 cur_arg++;
3412 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
3413 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
3414 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003415 }
3416
Christopher Faulet61cc8522020-04-20 14:54:42 +02003417 chk = calloc(1, sizeof(*chk));
3418 if (!chk) {
3419 memprintf(errmsg, "out of memory");
3420 goto error;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003421 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003422 chk->action = TCPCHK_ACT_ACTION_KW;
3423 chk->action_kw.rule = actrule;
3424 return chk;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003425
3426 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003427 free(actrule);
3428 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003429}
3430
Christopher Faulet61cc8522020-04-20 14:54:42 +02003431/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
3432 * returned on error.
3433 */
3434static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
3435 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003436{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003437 struct tcpcheck_rule *chk = NULL;
3438 struct sockaddr_storage *sk = NULL;
3439 char *comment = NULL, *sni = NULL, *alpn = NULL;
3440 struct sample_expr *port_expr = NULL;
3441 unsigned short conn_opts = 0;
3442 long port = 0;
3443 int alpn_len = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003444
Christopher Faulet61cc8522020-04-20 14:54:42 +02003445 list_for_each_entry(chk, rules, list) {
3446 if (chk->action == TCPCHK_ACT_CONNECT)
3447 break;
3448 if (chk->action == TCPCHK_ACT_COMMENT ||
3449 chk->action == TCPCHK_ACT_ACTION_KW ||
3450 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3451 continue;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003452
Christopher Faulet61cc8522020-04-20 14:54:42 +02003453 memprintf(errmsg, "first step MUST also be a 'connect', "
3454 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
3455 "when there is a 'connect' step in the tcp-check ruleset");
3456 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003457 }
3458
Christopher Faulet61cc8522020-04-20 14:54:42 +02003459 cur_arg++;
3460 while (*(args[cur_arg])) {
3461 if (strcmp(args[cur_arg], "default") == 0)
3462 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
3463 else if (strcmp(args[cur_arg], "addr") == 0) {
3464 int port1, port2;
3465 struct protocol *proto;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003466
Christopher Faulet61cc8522020-04-20 14:54:42 +02003467 if (!*(args[cur_arg+1])) {
3468 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
3469 goto error;
3470 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003471
Christopher Faulet61cc8522020-04-20 14:54:42 +02003472 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
3473 if (!sk) {
3474 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
3475 goto error;
3476 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003477
Christopher Faulet61cc8522020-04-20 14:54:42 +02003478 proto = protocol_by_family(sk->ss_family);
3479 if (!proto || !proto->connect) {
3480 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
3481 args[cur_arg]);
3482 goto error;
3483 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003484
Christopher Faulet61cc8522020-04-20 14:54:42 +02003485 if (port1 != port2) {
3486 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
3487 args[cur_arg], args[cur_arg+1]);
3488 goto error;
3489 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003490
Christopher Faulet61cc8522020-04-20 14:54:42 +02003491 cur_arg++;
3492 }
3493 else if (strcmp(args[cur_arg], "port") == 0) {
3494 const char *p, *end;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003495
Christopher Faulet61cc8522020-04-20 14:54:42 +02003496 if (!*(args[cur_arg+1])) {
3497 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
3498 goto error;
3499 }
3500 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003501
Christopher Faulet61cc8522020-04-20 14:54:42 +02003502 port = 0;
3503 release_sample_expr(port_expr);
3504 p = args[cur_arg]; end = p + strlen(p);
3505 port = read_uint(&p, end);
3506 if (p != end) {
3507 int idx = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003508
Christopher Faulet61cc8522020-04-20 14:54:42 +02003509 px->conf.args.ctx = ARGC_SRV;
3510 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3511 file, line, errmsg, &px->conf.args, NULL);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003512
Christopher Faulet61cc8522020-04-20 14:54:42 +02003513 if (!port_expr) {
3514 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
3515 goto error;
3516 }
3517 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3518 memprintf(errmsg, "error detected while parsing port expression : "
3519 " fetch method '%s' extracts information from '%s', "
3520 "none of which is available here.\n",
3521 args[cur_arg], sample_src_names(port_expr->fetch->use));
3522 goto error;
3523 }
3524 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
3525 }
3526 else if (port > 65535 || port < 1) {
3527 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
3528 args[cur_arg]);
3529 goto error;
3530 }
3531 }
3532 else if (strcmp(args[cur_arg], "comment") == 0) {
3533 if (!*(args[cur_arg+1])) {
3534 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3535 goto error;
3536 }
3537 cur_arg++;
3538 free(comment);
3539 comment = strdup(args[cur_arg]);
3540 if (!comment) {
3541 memprintf(errmsg, "out of memory");
3542 goto error;
3543 }
3544 }
3545 else if (strcmp(args[cur_arg], "send-proxy") == 0)
3546 conn_opts |= TCPCHK_OPT_SEND_PROXY;
3547 else if (strcmp(args[cur_arg], "via-socks4") == 0)
3548 conn_opts |= TCPCHK_OPT_SOCKS4;
3549 else if (strcmp(args[cur_arg], "linger") == 0)
3550 conn_opts |= TCPCHK_OPT_LINGER;
3551#ifdef USE_OPENSSL
3552 else if (strcmp(args[cur_arg], "ssl") == 0) {
3553 px->options |= PR_O_TCPCHK_SSL;
3554 conn_opts |= TCPCHK_OPT_SSL;
3555 }
3556 else if (strcmp(args[cur_arg], "sni") == 0) {
3557 if (!*(args[cur_arg+1])) {
3558 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3559 goto error;
3560 }
3561 cur_arg++;
3562 free(sni);
3563 sni = strdup(args[cur_arg]);
3564 if (!sni) {
3565 memprintf(errmsg, "out of memory");
3566 goto error;
3567 }
3568 }
3569 else if (strcmp(args[cur_arg], "alpn") == 0) {
3570#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
3571 free(alpn);
3572 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
3573 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
3574 goto error;
3575 }
3576 cur_arg++;
3577#else
3578 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003579 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003580#endif
3581 }
3582#endif /* USE_OPENSSL */
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003583
Christopher Faulet61cc8522020-04-20 14:54:42 +02003584 else {
3585 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
3586#ifdef USE_OPENSSL
3587 ", 'ssl', 'sni', 'alpn'"
3588#endif /* USE_OPENSSL */
3589 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
3590 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003591 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003592 }
3593 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003594 }
3595
Christopher Faulet61cc8522020-04-20 14:54:42 +02003596 chk = calloc(1, sizeof(*chk));
3597 if (!chk) {
3598 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003599 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003600 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003601 chk->action = TCPCHK_ACT_CONNECT;
3602 chk->comment = comment;
3603 chk->connect.port = port;
3604 chk->connect.options = conn_opts;
3605 chk->connect.sni = sni;
3606 chk->connect.alpn = alpn;
3607 chk->connect.alpn_len= alpn_len;
3608 chk->connect.port_expr= port_expr;
3609 if (sk)
3610 chk->connect.addr = *sk;
3611 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003612
Christopher Faulet61cc8522020-04-20 14:54:42 +02003613 error:
3614 free(alpn);
3615 free(sni);
3616 free(comment);
3617 release_sample_expr(port_expr);
3618 return NULL;
3619}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003620
Christopher Faulet61cc8522020-04-20 14:54:42 +02003621/* Parses and creates a tcp-check send rule. NULL is returned on error */
3622static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
3623 const char *file, int line, char **errmsg)
3624{
3625 struct tcpcheck_rule *chk = NULL;
3626 char *comment = NULL, *data = NULL;
3627 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003628
Christopher Faulet61cc8522020-04-20 14:54:42 +02003629 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
3630 if (!*(args[cur_arg+1])) {
3631 memprintf(errmsg, "'%s' expects a %s as argument",
3632 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003633 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003634 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003635
Christopher Faulet61cc8522020-04-20 14:54:42 +02003636 data = args[cur_arg+1];
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003637
Christopher Faulet61cc8522020-04-20 14:54:42 +02003638 cur_arg += 2;
3639 while (*(args[cur_arg])) {
3640 if (strcmp(args[cur_arg], "comment") == 0) {
3641 if (!*(args[cur_arg+1])) {
3642 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3643 goto error;
3644 }
3645 cur_arg++;
3646 free(comment);
3647 comment = strdup(args[cur_arg]);
3648 if (!comment) {
3649 memprintf(errmsg, "out of memory");
3650 goto error;
3651 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003652 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003653 else if (strcmp(args[cur_arg], "log-format") == 0) {
3654 if (type == TCPCHK_SEND_BINARY)
3655 type = TCPCHK_SEND_BINARY_LF;
3656 else if (type == TCPCHK_SEND_STRING)
3657 type = TCPCHK_SEND_STRING_LF;
3658 }
3659 else {
3660 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
3661 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003662 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003663 }
3664 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003665 }
3666
Christopher Faulet61cc8522020-04-20 14:54:42 +02003667 chk = calloc(1, sizeof(*chk));
3668 if (!chk) {
3669 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003670 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003671 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003672 chk->action = TCPCHK_ACT_SEND;
3673 chk->comment = comment;
3674 chk->send.type = type;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003675
Christopher Faulet61cc8522020-04-20 14:54:42 +02003676 switch (chk->send.type) {
3677 case TCPCHK_SEND_STRING:
3678 chk->send.data = ist2(strdup(data), strlen(data));
3679 if (!isttest(chk->send.data)) {
3680 memprintf(errmsg, "out of memory");
3681 goto error;
3682 }
3683 break;
3684 case TCPCHK_SEND_BINARY:
3685 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
3686 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
3687 goto error;
3688 }
3689 break;
3690 case TCPCHK_SEND_STRING_LF:
3691 case TCPCHK_SEND_BINARY_LF:
3692 LIST_INIT(&chk->send.fmt);
3693 px->conf.args.ctx = ARGC_SRV;
3694 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3695 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
3696 goto error;
3697 }
3698 break;
3699 case TCPCHK_SEND_HTTP:
3700 case TCPCHK_SEND_UNDEF:
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003701 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003702 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003703
Christopher Faulet61cc8522020-04-20 14:54:42 +02003704 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003705
Christopher Faulet61cc8522020-04-20 14:54:42 +02003706 error:
3707 free(chk);
3708 free(comment);
3709 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003710}
3711
Christopher Faulet61cc8522020-04-20 14:54:42 +02003712/* Parses and creates a http-check send rule. NULL is returned on error */
3713static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
3714 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003715{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003716 struct tcpcheck_rule *chk = NULL;
3717 struct tcpcheck_http_hdr *hdr = NULL;
3718 struct http_hdr hdrs[global.tune.max_http_hdr];
3719 char *meth = NULL, *uri = NULL, *vsn = NULL;
3720 char *body = NULL, *comment = NULL;
3721 unsigned int flags = 0;
3722 int i = 0;
3723
3724 cur_arg++;
3725 while (*(args[cur_arg])) {
3726 if (strcmp(args[cur_arg], "meth") == 0) {
3727 if (!*(args[cur_arg+1])) {
3728 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3729 goto error;
3730 }
3731 cur_arg++;
3732 meth = args[cur_arg];
3733 }
3734 else if (strcmp(args[cur_arg], "uri") == 0) {
3735 if (!*(args[cur_arg+1])) {
3736 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3737 goto error;
3738 }
3739 cur_arg++;
3740 uri = args[cur_arg];
3741 // TODO: log-format uri
3742 }
3743 else if (strcmp(args[cur_arg], "vsn") == 0) {
3744 if (!*(args[cur_arg+1])) {
3745 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3746 goto error;
3747 }
3748 cur_arg++;
3749 vsn = args[cur_arg];
3750 }
3751 else if (strcmp(args[cur_arg], "hdr") == 0) {
3752 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
3753 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
3754 goto error;
3755 }
3756 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
3757 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
3758 i++;
3759 cur_arg += 2;
3760 }
3761 else if (strcmp(args[cur_arg], "body") == 0) {
3762 if (!*(args[cur_arg+1])) {
3763 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3764 goto error;
3765 }
3766 cur_arg++;
3767 body = args[cur_arg];
3768 // TODO: log-format body
3769 }
3770 else if (strcmp(args[cur_arg], "comment") == 0) {
3771 if (!*(args[cur_arg+1])) {
3772 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3773 goto error;
3774 }
3775 cur_arg++;
3776 free(comment);
3777 comment = strdup(args[cur_arg]);
3778 if (!comment) {
3779 memprintf(errmsg, "out of memory");
3780 goto error;
3781 }
3782 }
3783 else {
3784 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'hdr' and 'body' but got '%s' as argument.",
3785 args[cur_arg]);
3786 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003787 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003788 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003789 }
3790
Christopher Faulet61cc8522020-04-20 14:54:42 +02003791 hdrs[i].n = hdrs[i].v = IST_NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003792
Christopher Faulet61cc8522020-04-20 14:54:42 +02003793 chk = calloc(1, sizeof(*chk));
3794 if (!chk) {
3795 memprintf(errmsg, "out of memory");
3796 goto error;
3797 }
3798 chk->action = TCPCHK_ACT_SEND;
3799 chk->comment = comment; comment = NULL;
3800 chk->send.type = TCPCHK_SEND_HTTP;
3801 chk->send.http.flags = flags;
3802 LIST_INIT(&chk->send.http.hdrs);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003803
Christopher Faulet61cc8522020-04-20 14:54:42 +02003804 if (meth) {
3805 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
3806 chk->send.http.meth.str.area = strdup(meth);
3807 chk->send.http.meth.str.data = strlen(meth);
3808 if (!chk->send.http.meth.str.area) {
3809 memprintf(errmsg, "out of memory");
3810 goto error;
3811 }
3812 }
3813 if (uri) {
3814 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
3815 if (!isttest(chk->send.http.uri)) {
3816 memprintf(errmsg, "out of memory");
3817 goto error;
3818 }
3819 }
3820 if (vsn) {
3821 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
3822 if (!isttest(chk->send.http.vsn)) {
3823 memprintf(errmsg, "out of memory");
3824 goto error;
3825 }
3826 }
Christopher Fauletb61caf42020-04-21 10:57:42 +02003827 for (i = 0; istlen(hdrs[i].n); i++) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003828 hdr = calloc(1, sizeof(*hdr));
3829 if (!hdr) {
3830 memprintf(errmsg, "out of memory");
3831 goto error;
3832 }
3833 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02003834 hdr->name = istdup(hdrs[i].n);
3835 if (!isttest(hdr->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003836 memprintf(errmsg, "out of memory");
3837 goto error;
3838 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003839
Christopher Fauletb61caf42020-04-21 10:57:42 +02003840 ist0(hdrs[i].v);
3841 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 +02003842 goto error;
3843 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
3844 hdr = NULL;
3845 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003846
Christopher Faulet61cc8522020-04-20 14:54:42 +02003847 if (body) {
3848 chk->send.http.body = ist2(strdup(body), strlen(body));
3849 if (!isttest(chk->send.http.body)) {
3850 memprintf(errmsg, "out of memory");
3851 goto error;
3852 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003853 }
3854
Christopher Faulet61cc8522020-04-20 14:54:42 +02003855 return chk;
3856
3857 error:
3858 free_tcpcheck_http_hdr(hdr);
3859 free_tcpcheck(chk, 0);
3860 free(comment);
3861 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003862}
3863
Christopher Faulet61cc8522020-04-20 14:54:42 +02003864/* Parses and creates a http-check comment rule. NULL is returned on error */
3865static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
3866 const char *file, int line, char **errmsg)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003867{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003868 struct tcpcheck_rule *chk = NULL;
3869 char *comment = NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003870
Christopher Faulet61cc8522020-04-20 14:54:42 +02003871 if (!*(args[cur_arg+1])) {
3872 memprintf(errmsg, "expects a string as argument");
3873 goto error;
3874 }
3875 cur_arg++;
3876 comment = strdup(args[cur_arg]);
3877 if (!comment) {
3878 memprintf(errmsg, "out of memory");
3879 goto error;
3880 }
Willy Tarreau04276f32017-01-06 17:41:29 +01003881
Christopher Faulet61cc8522020-04-20 14:54:42 +02003882 chk = calloc(1, sizeof(*chk));
3883 if (!chk) {
3884 memprintf(errmsg, "out of memory");
3885 goto error;
3886 }
3887 chk->action = TCPCHK_ACT_COMMENT;
3888 chk->comment = comment;
3889 return chk;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003890
Christopher Faulet61cc8522020-04-20 14:54:42 +02003891 error:
3892 free(comment);
3893 return NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003894}
3895
Christopher Faulet61cc8522020-04-20 14:54:42 +02003896/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
3897 * on error. <proto> is set to the right protocol flags (covered by the
3898 * TCPCHK_RULES_PROTO_CHK mask).
3899 */
3900static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
3901 struct list *rules, unsigned int proto,
3902 const char *file, int line, char **errmsg)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003903{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003904 struct tcpcheck_rule *prev_check, *chk = NULL;
3905 struct sample_expr *status_expr = NULL;
3906 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
3907 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
3908 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
3909 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
3910 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
3911 long min_recv = -1;
Christopher Faulet88d939c2020-04-22 15:32:11 +02003912 int inverse = 0;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003913
Christopher Faulet61cc8522020-04-20 14:54:42 +02003914 str = on_success_msg = on_error_msg = comment = pattern = NULL;
3915 if (!*(args[cur_arg+1])) {
3916 memprintf(errmsg, "expects at least a matching pattern as arguments");
3917 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003918 }
3919
Christopher Faulet61cc8522020-04-20 14:54:42 +02003920 cur_arg++;
3921 while (*(args[cur_arg])) {
3922 int in_pattern = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02003923
Christopher Faulet61cc8522020-04-20 14:54:42 +02003924 rescan:
3925 if (strcmp(args[cur_arg], "min-recv") == 0) {
3926 if (in_pattern) {
3927 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3928 goto error;
3929 }
3930 if (!*(args[cur_arg+1])) {
3931 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
3932 goto error;
3933 }
3934 /* Use an signed integer here because of chksize */
3935 cur_arg++;
3936 min_recv = atol(args[cur_arg]);
3937 if (min_recv < -1 || min_recv > INT_MAX) {
3938 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
3939 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02003940 }
3941 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003942 else if (*(args[cur_arg]) == '!') {
3943 in_pattern = 1;
3944 while (*(args[cur_arg]) == '!') {
3945 inverse = !inverse;
3946 args[cur_arg]++;
Christopher Faulete5870d82020-04-15 11:32:03 +02003947 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003948 if (!*(args[cur_arg]))
3949 cur_arg++;
3950 goto rescan;
Christopher Faulete5870d82020-04-15 11:32:03 +02003951 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003952 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
3953 if (type != TCPCHK_EXPECT_UNDEF) {
3954 memprintf(errmsg, "only on pattern expected");
3955 goto error;
3956 }
3957 if (proto != TCPCHK_RULES_HTTP_CHK)
3958 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
3959 else
3960 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Faulete5870d82020-04-15 11:32:03 +02003961
Christopher Faulet61cc8522020-04-20 14:54:42 +02003962 if (!*(args[cur_arg+1])) {
3963 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3964 goto error;
3965 }
3966 cur_arg++;
3967 pattern = args[cur_arg];
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003968 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003969 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
3970 if (proto == TCPCHK_RULES_HTTP_CHK)
3971 goto bad_http_kw;
3972 if (type != TCPCHK_EXPECT_UNDEF) {
3973 memprintf(errmsg, "only on pattern expected");
3974 goto error;
3975 }
3976 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003977
Christopher Faulet61cc8522020-04-20 14:54:42 +02003978 if (!*(args[cur_arg+1])) {
3979 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3980 goto error;
3981 }
3982 cur_arg++;
3983 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003984 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003985 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3986 if (proto != TCPCHK_RULES_HTTP_CHK)
3987 goto bad_tcp_kw;
3988 if (type != TCPCHK_EXPECT_UNDEF) {
3989 memprintf(errmsg, "only on pattern expected");
3990 goto error;
3991 }
3992 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003993
Christopher Faulet61cc8522020-04-20 14:54:42 +02003994 if (!*(args[cur_arg+1])) {
3995 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3996 goto error;
3997 }
3998 cur_arg++;
3999 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004000 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004001 else if (strcmp(args[cur_arg], "custom") == 0) {
4002 if (in_pattern) {
4003 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4004 goto error;
4005 }
4006 if (type != TCPCHK_EXPECT_UNDEF) {
4007 memprintf(errmsg, "only on pattern expected");
4008 goto error;
4009 }
4010 type = TCPCHK_EXPECT_CUSTOM;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004011 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004012 else if (strcmp(args[cur_arg], "comment") == 0) {
4013 if (in_pattern) {
4014 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4015 goto error;
4016 }
4017 if (!*(args[cur_arg+1])) {
4018 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4019 goto error;
4020 }
4021 cur_arg++;
4022 free(comment);
4023 comment = strdup(args[cur_arg]);
4024 if (!comment) {
4025 memprintf(errmsg, "out of memory");
4026 goto error;
4027 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004028 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004029 else if (strcmp(args[cur_arg], "on-success") == 0) {
4030 if (in_pattern) {
4031 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4032 goto error;
4033 }
4034 if (!*(args[cur_arg+1])) {
4035 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4036 goto error;
4037 }
4038 cur_arg++;
4039 free(on_success_msg);
4040 on_success_msg = strdup(args[cur_arg]);
4041 if (!on_success_msg) {
4042 memprintf(errmsg, "out of memory");
4043 goto error;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004044 }
4045 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004046 else if (strcmp(args[cur_arg], "on-error") == 0) {
4047 if (in_pattern) {
4048 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4049 goto error;
4050 }
4051 if (!*(args[cur_arg+1])) {
4052 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4053 goto error;
4054 }
4055 cur_arg++;
4056 free(on_error_msg);
4057 on_error_msg = strdup(args[cur_arg]);
4058 if (!on_error_msg) {
4059 memprintf(errmsg, "out of memory");
4060 goto error;
4061 }
4062 }
4063 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4064 if (in_pattern) {
4065 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4066 goto error;
4067 }
4068 if (!*(args[cur_arg+1])) {
4069 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4070 goto error;
4071 }
4072 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4073 ok_st = HCHK_STATUS_L7OKD;
4074 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4075 ok_st = HCHK_STATUS_L7OKCD;
4076 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4077 ok_st = HCHK_STATUS_L6OK;
4078 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4079 ok_st = HCHK_STATUS_L4OK;
4080 else {
4081 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4082 args[cur_arg], args[cur_arg+1]);
4083 goto error;
4084 }
4085 cur_arg++;
4086 }
4087 else if (strcmp(args[cur_arg], "error-status") == 0) {
4088 if (in_pattern) {
4089 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4090 goto error;
4091 }
4092 if (!*(args[cur_arg+1])) {
4093 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4094 goto error;
4095 }
4096 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4097 err_st = HCHK_STATUS_L7RSP;
4098 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4099 err_st = HCHK_STATUS_L7STS;
4100 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4101 err_st = HCHK_STATUS_L6RSP;
4102 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4103 err_st = HCHK_STATUS_L4CON;
4104 else {
4105 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4106 args[cur_arg], args[cur_arg+1]);
4107 goto error;
4108 }
4109 cur_arg++;
4110 }
4111 else if (strcmp(args[cur_arg], "status-code") == 0) {
4112 int idx = 0;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004113
Christopher Faulet61cc8522020-04-20 14:54:42 +02004114 if (in_pattern) {
4115 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4116 goto error;
4117 }
4118 if (!*(args[cur_arg+1])) {
4119 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4120 goto error;
4121 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004122
Christopher Faulet61cc8522020-04-20 14:54:42 +02004123 cur_arg++;
4124 release_sample_expr(status_expr);
4125 px->conf.args.ctx = ARGC_SRV;
4126 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4127 file, line, errmsg, &px->conf.args, NULL);
4128 if (!status_expr) {
4129 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4130 goto error;
4131 }
4132 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4133 memprintf(errmsg, "error detected while parsing status-code expression : "
4134 " fetch method '%s' extracts information from '%s', "
4135 "none of which is available here.\n",
4136 args[cur_arg], sample_src_names(status_expr->fetch->use));
4137 goto error;
4138 }
4139 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
4140 }
4141 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4142 if (in_pattern) {
4143 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4144 goto error;
4145 }
4146 if (!*(args[cur_arg+1])) {
4147 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4148 goto error;
4149 }
4150 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4151 tout_st = HCHK_STATUS_L7TOUT;
4152 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4153 tout_st = HCHK_STATUS_L6TOUT;
4154 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4155 tout_st = HCHK_STATUS_L4TOUT;
4156 else {
4157 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4158 args[cur_arg], args[cur_arg+1]);
4159 goto error;
4160 }
4161 cur_arg++;
4162 }
4163 else {
4164 if (proto == TCPCHK_RULES_HTTP_CHK) {
4165 bad_http_kw:
4166 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
4167 " or comment but got '%s' as argument.", args[cur_arg]);
4168 }
4169 else {
4170 bad_tcp_kw:
4171 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4172 " or comment but got '%s' as argument.", args[cur_arg]);
4173 }
4174 goto error;
4175 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004176
Christopher Faulet61cc8522020-04-20 14:54:42 +02004177 cur_arg++;
4178 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004179
Christopher Faulet61cc8522020-04-20 14:54:42 +02004180 chk = calloc(1, sizeof(*chk));
4181 if (!chk) {
4182 memprintf(errmsg, "out of memory");
4183 goto error;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004184 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004185 chk->action = TCPCHK_ACT_EXPECT;
4186 LIST_INIT(&chk->expect.onerror_fmt);
4187 LIST_INIT(&chk->expect.onsuccess_fmt);
4188 chk->comment = comment; comment = NULL;
4189 chk->expect.type = type;
4190 chk->expect.min_recv = min_recv;
4191 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004192 chk->expect.ok_status = ok_st;
4193 chk->expect.err_status = err_st;
4194 chk->expect.tout_status = tout_st;
4195 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004196
Christopher Faulet61cc8522020-04-20 14:54:42 +02004197 if (on_success_msg) {
4198 px->conf.args.ctx = ARGC_SRV;
4199 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4200 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4201 goto error;
4202 }
4203 free(on_success_msg);
4204 on_success_msg = NULL;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004205 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004206 if (on_error_msg) {
4207 px->conf.args.ctx = ARGC_SRV;
4208 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4209 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4210 goto error;
4211 }
4212 free(on_error_msg);
4213 on_error_msg = NULL;
4214 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004215
Christopher Faulet61cc8522020-04-20 14:54:42 +02004216 switch (chk->expect.type) {
4217 case TCPCHK_EXPECT_STRING:
4218 case TCPCHK_EXPECT_HTTP_STATUS:
4219 case TCPCHK_EXPECT_HTTP_BODY:
4220 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
Christopher Fauletb61caf42020-04-21 10:57:42 +02004221 if (!isttest(chk->expect.data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004222 memprintf(errmsg, "out of memory");
4223 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004224 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004225 break;
4226 case TCPCHK_EXPECT_BINARY:
4227 if (parse_binary(pattern, &chk->expect.data.ptr, (int *)&chk->expect.data.len, errmsg) == 0) {
4228 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4229 goto error;
4230 }
4231 case TCPCHK_EXPECT_REGEX:
4232 case TCPCHK_EXPECT_REGEX_BINARY:
4233 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
4234 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Faulet88d939c2020-04-22 15:32:11 +02004235 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004236 if (!chk->expect.regex)
4237 goto error;
4238 break;
4239 case TCPCHK_EXPECT_CUSTOM:
4240 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4241 break;
4242 case TCPCHK_EXPECT_UNDEF:
4243 free(chk);
4244 memprintf(errmsg, "pattern not found");
4245 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004246 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004247
Christopher Faulet61cc8522020-04-20 14:54:42 +02004248 /* All tcp-check expect points back to the first inverse expect rule in
4249 * a chain of one or more expect rule, potentially itself.
4250 */
4251 chk->expect.head = chk;
4252 list_for_each_entry_rev(prev_check, rules, list) {
4253 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4254 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
4255 chk->expect.head = prev_check;
4256 continue;
4257 }
4258 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
4259 break;
Christopher Faulet404f9192020-04-09 23:13:54 +02004260 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004261 return chk;
4262
4263 error:
4264 free_tcpcheck(chk, 0);
4265 free(str);
4266 free(comment);
4267 free(on_success_msg);
4268 free(on_error_msg);
4269 release_sample_expr(status_expr);
Christopher Faulet404f9192020-04-09 23:13:54 +02004270 return NULL;
4271}
4272
Christopher Faulet61cc8522020-04-20 14:54:42 +02004273/* Overwrites fields of the old http send rule with those of the new one. When
4274 * replaced, old values are freed and replaced by the new ones. New values are
4275 * not copied but transferred. At the end <new> should be empty and can be
4276 * safely released. This function never fails.
4277 */
4278static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
Christopher Faulet404f9192020-04-09 23:13:54 +02004279{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004280 struct logformat_node *lf, *lfb;
4281 struct tcpcheck_http_hdr *hdr, *bhdr;
Christopher Faulet404f9192020-04-09 23:13:54 +02004282
Christopher Faulet404f9192020-04-09 23:13:54 +02004283
Christopher Faulet61cc8522020-04-20 14:54:42 +02004284 if (new->send.http.meth.str.area) {
4285 free(old->send.http.meth.str.area);
4286 old->send.http.meth.meth = new->send.http.meth.meth;
4287 old->send.http.meth.str.area = new->send.http.meth.str.area;
4288 old->send.http.meth.str.data = new->send.http.meth.str.data;
4289 new->send.http.meth.str = BUF_NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02004290 }
4291
Christopher Faulet61cc8522020-04-20 14:54:42 +02004292 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4293 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004294 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004295 else
4296 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4297 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4298 old->send.http.uri = new->send.http.uri;
4299 new->send.http.uri = IST_NULL;
4300 }
4301 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4302 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004303 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004304 else
4305 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4306 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4307 LIST_INIT(&old->send.http.uri_fmt);
4308 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4309 LIST_DEL(&lf->list);
4310 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4311 }
4312 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004313
Christopher Faulet61cc8522020-04-20 14:54:42 +02004314 if (isttest(new->send.http.vsn)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02004315 istfree(&old->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004316 old->send.http.vsn = new->send.http.vsn;
4317 new->send.http.vsn = IST_NULL;
4318 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004319
Christopher Faulet61cc8522020-04-20 14:54:42 +02004320 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4321 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4322 LIST_DEL(&hdr->list);
4323 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
Christopher Faulet404f9192020-04-09 23:13:54 +02004324 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004325
4326 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4327 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004328 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004329 else
4330 free_tcpcheck_fmt(&old->send.http.body_fmt);
4331 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4332 old->send.http.body = new->send.http.body;
4333 new->send.http.body = IST_NULL;
4334 }
4335 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4336 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004337 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004338 else
4339 free_tcpcheck_fmt(&old->send.http.body_fmt);
4340 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4341 LIST_INIT(&old->send.http.body_fmt);
4342 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4343 LIST_DEL(&lf->list);
4344 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4345 }
4346 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004347}
4348
Christopher Faulet61cc8522020-04-20 14:54:42 +02004349/* Internal function used to add an http-check rule in a list during the config
4350 * parsing step. Depending on its type, and the previously inserted rules, a
4351 * specific action may be performed or an error may be reported. This functions
4352 * returns 1 on success and 0 on error and <errmsg> is filled with the error
4353 * message.
4354 */
4355static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004356{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004357 struct tcpcheck_rule *r;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004358
Christopher Faulet61cc8522020-04-20 14:54:42 +02004359 /* the implicit send rule coming from an "option httpchk" line must be
4360 * merged with the first explici http-check send rule, if
4361 * any. Depdending the declaration order some tests are required.
4362 *
4363 * Some tests is also required for other kinds of http-check rules to be
4364 * sure the ruleset remains valid.
4365 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004366
Christopher Faulet61cc8522020-04-20 14:54:42 +02004367 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4368 /* Tries to add an implcit http-check send rule from an "option httpchk" line.
4369 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
4370 * following tests are performed :
4371 *
4372 * 1- If there is no such rule or if it is not a send rule, the implicit send
4373 * rule is pushed in front of the ruleset
4374 *
4375 * 2- If it is another implicit send rule, it is replaced with the new one.
4376 *
4377 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
4378 * both, overwritting the old send rule (the explicit one) with info of the
4379 * new send rule (the implicit one).
4380 */
4381 r = get_first_tcpcheck_rule(rules);
4382 if (r && r->action == TCPCHK_ACT_CONNECT)
4383 r = get_next_tcpcheck_rule(rules, r);
4384 if (!r || r->action != TCPCHK_ACT_SEND)
4385 LIST_ADD(rules->list, &chk->list);
4386 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
4387 LIST_DEL(&r->list);
4388 free_tcpcheck(r, 0);
4389 LIST_ADD(rules->list, &chk->list);
4390 }
4391 else {
4392 tcpcheck_overwrite_send_http_rule(r, chk);
4393 free_tcpcheck(chk, 0);
4394 }
4395 }
4396 else {
4397 /* Tries to add an explicit http-check rule. First of all we check the typefo the
4398 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
4399 * with an existing implicit send rule, if any. At the end, if there is no error,
4400 * the rule is appended to the list.
4401 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004402
Christopher Faulet61cc8522020-04-20 14:54:42 +02004403 r = get_last_tcpcheck_rule(rules);
4404 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4405 /* no error */;
4406 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
4407 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
4408 chk->index+1);
4409 return 0;
4410 }
4411 else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
4412 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
4413 chk->index+1);
4414 return 0;
4415 }
4416 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
4417 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
4418 chk->index+1);
4419 return 0;
4420 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004421
Christopher Faulet61cc8522020-04-20 14:54:42 +02004422 if (chk->action == TCPCHK_ACT_SEND) {
4423 r = get_first_tcpcheck_rule(rules);
4424 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4425 tcpcheck_overwrite_send_http_rule(r, chk);
4426 free_tcpcheck(chk, 0);
4427 LIST_DEL(&r->list);
4428 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
4429 chk = r;
4430 }
4431 }
4432 LIST_ADDQ(rules->list, &chk->list);
4433 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004434 return 1;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004435}
4436
Christopher Faulet61cc8522020-04-20 14:54:42 +02004437/**************************************************************************/
4438/************************** Init/deinit checks ****************************/
4439/**************************************************************************/
4440static const char *init_check(struct check *check, int type)
4441{
4442 check->type = type;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004443
Christopher Faulet61cc8522020-04-20 14:54:42 +02004444 b_reset(&check->bi); check->bi.size = global.tune.chksize;
4445 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004446
Christopher Faulet61cc8522020-04-20 14:54:42 +02004447 check->bi.area = calloc(check->bi.size, sizeof(char));
4448 check->bo.area = calloc(check->bo.size, sizeof(char));
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004449
Christopher Faulet61cc8522020-04-20 14:54:42 +02004450 if (!check->bi.area || !check->bo.area)
4451 return "out of memory while allocating check buffer";
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004452
Christopher Faulet61cc8522020-04-20 14:54:42 +02004453 check->wait_list.tasklet = tasklet_new();
4454 if (!check->wait_list.tasklet)
4455 return "out of memory while allocating check tasklet";
4456 check->wait_list.events = 0;
4457 check->wait_list.tasklet->process = event_srv_chk_io;
4458 check->wait_list.tasklet->context = check;
4459 return NULL;
4460}
4461
4462void free_check(struct check *check)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004463{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004464 task_destroy(check->task);
4465 if (check->wait_list.tasklet)
4466 tasklet_free(check->wait_list.tasklet);
4467
4468 free(check->bi.area);
4469 free(check->bo.area);
4470 if (check->cs) {
4471 free(check->cs->conn);
4472 check->cs->conn = NULL;
4473 cs_free(check->cs);
4474 check->cs = NULL;
4475 }
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004476}
4477
Christopher Faulet61cc8522020-04-20 14:54:42 +02004478/* manages a server health-check. Returns the time the task accepts to wait, or
4479 * TIME_ETERNITY for infinity.
4480 */
4481static struct task *process_chk(struct task *t, void *context, unsigned short state)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004482{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004483 struct check *check = context;
4484
4485 if (check->type == PR_O2_EXT_CHK)
4486 return process_chk_proc(t, context, state);
4487 return process_chk_conn(t, context, state);
4488
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004489}
4490
Christopher Faulet61cc8522020-04-20 14:54:42 +02004491
4492static int start_check_task(struct check *check, int mininter,
4493 int nbcheck, int srvpos)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004494{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004495 struct task *t;
4496 unsigned long thread_mask = MAX_THREADS_MASK;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004497
Christopher Faulet61cc8522020-04-20 14:54:42 +02004498 if (check->type == PR_O2_EXT_CHK)
4499 thread_mask = 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004500
Christopher Faulet61cc8522020-04-20 14:54:42 +02004501 /* task for the check */
4502 if ((t = task_new(thread_mask)) == NULL) {
4503 ha_alert("Starting [%s:%s] check: out of memory.\n",
4504 check->server->proxy->id, check->server->id);
4505 return 0;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004506 }
4507
Christopher Faulet61cc8522020-04-20 14:54:42 +02004508 check->task = t;
4509 t->process = process_chk;
4510 t->context = check;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004511
Christopher Faulet61cc8522020-04-20 14:54:42 +02004512 if (mininter < srv_getinter(check))
4513 mininter = srv_getinter(check);
4514
4515 if (global.max_spread_checks && mininter > global.max_spread_checks)
4516 mininter = global.max_spread_checks;
4517
4518 /* check this every ms */
4519 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
4520 check->start = now;
4521 task_queue(t);
4522
4523 return 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004524}
4525
Christopher Faulet61cc8522020-04-20 14:54:42 +02004526/* updates the server's weight during a warmup stage. Once the final weight is
4527 * reached, the task automatically stops. Note that any server status change
4528 * must have updated s->last_change accordingly.
4529 */
4530static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004531{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004532 struct server *s = context;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004533
Christopher Faulet61cc8522020-04-20 14:54:42 +02004534 /* by default, plan on stopping the task */
4535 t->expire = TICK_ETERNITY;
4536 if ((s->next_admin & SRV_ADMF_MAINT) ||
4537 (s->next_state != SRV_ST_STARTING))
4538 return t;
Christopher Faulete5870d82020-04-15 11:32:03 +02004539
Christopher Faulet61cc8522020-04-20 14:54:42 +02004540 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004541
Christopher Faulet61cc8522020-04-20 14:54:42 +02004542 /* recalculate the weights and update the state */
4543 server_recalc_eweight(s, 1);
Christopher Faulet5c288742020-03-31 08:15:58 +02004544
Christopher Faulet61cc8522020-04-20 14:54:42 +02004545 /* probably that we can refill this server with a bit more connections */
4546 pendconn_grab_from_px(s);
Christopher Faulet5c288742020-03-31 08:15:58 +02004547
Christopher Faulet61cc8522020-04-20 14:54:42 +02004548 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
Christopher Faulet5c288742020-03-31 08:15:58 +02004549
Christopher Faulet61cc8522020-04-20 14:54:42 +02004550 /* get back there in 1 second or 1/20th of the slowstart interval,
4551 * whichever is greater, resulting in small 5% steps.
4552 */
4553 if (s->next_state == SRV_ST_STARTING)
4554 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
4555 return t;
4556}
4557
4558/*
4559 * Start health-check.
4560 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
4561 */
4562static int start_checks()
4563{
4564
4565 struct proxy *px;
4566 struct server *s;
4567 struct task *t;
4568 int nbcheck=0, mininter=0, srvpos=0;
4569
4570 /* 0- init the dummy frontend used to create all checks sessions */
4571 init_new_proxy(&checks_fe);
4572 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
4573 checks_fe.mode = PR_MODE_TCP;
4574 checks_fe.maxconn = 0;
4575 checks_fe.conn_retries = CONN_RETRIES;
4576 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
4577 checks_fe.timeout.client = TICK_ETERNITY;
4578
4579 /* 1- count the checkers to run simultaneously.
4580 * We also determine the minimum interval among all of those which
4581 * have an interval larger than SRV_CHK_INTER_THRES. This interval
4582 * will be used to spread their start-up date. Those which have
4583 * a shorter interval will start independently and will not dictate
4584 * too short an interval for all others.
4585 */
4586 for (px = proxies_list; px; px = px->next) {
4587 for (s = px->srv; s; s = s->next) {
4588 if (s->slowstart) {
4589 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
4590 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
4591 return ERR_ALERT | ERR_FATAL;
4592 }
4593 /* We need a warmup task that will be called when the server
4594 * state switches from down to up.
4595 */
4596 s->warmup = t;
4597 t->process = server_warmup;
4598 t->context = s;
4599 /* server can be in this state only because of */
4600 if (s->next_state == SRV_ST_STARTING)
4601 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 +02004602 }
4603
Christopher Faulet61cc8522020-04-20 14:54:42 +02004604 if (s->check.state & CHK_ST_CONFIGURED) {
4605 nbcheck++;
4606 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
4607 (!mininter || mininter > srv_getinter(&s->check)))
4608 mininter = srv_getinter(&s->check);
Christopher Faulet5c288742020-03-31 08:15:58 +02004609 }
4610
Christopher Faulet61cc8522020-04-20 14:54:42 +02004611 if (s->agent.state & CHK_ST_CONFIGURED) {
4612 nbcheck++;
4613 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
4614 (!mininter || mininter > srv_getinter(&s->agent)))
4615 mininter = srv_getinter(&s->agent);
4616 }
Christopher Faulet5c288742020-03-31 08:15:58 +02004617 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004618 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004619
Christopher Faulet61cc8522020-04-20 14:54:42 +02004620 if (!nbcheck)
4621 return 0;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004622
Christopher Faulet61cc8522020-04-20 14:54:42 +02004623 srand((unsigned)time(NULL));
Christopher Fauletb7d30092020-03-30 15:19:03 +02004624
Christopher Faulet61cc8522020-04-20 14:54:42 +02004625 /*
4626 * 2- start them as far as possible from each others. For this, we will
4627 * start them after their interval set to the min interval divided by
4628 * the number of servers, weighted by the server's position in the list.
4629 */
4630 for (px = proxies_list; px; px = px->next) {
4631 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
4632 if (init_pid_list()) {
4633 ha_alert("Starting [%s] check: out of memory.\n", px->id);
4634 return ERR_ALERT | ERR_FATAL;
4635 }
4636 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004637
Christopher Faulet61cc8522020-04-20 14:54:42 +02004638 for (s = px->srv; s; s = s->next) {
4639 /* A task for the main check */
4640 if (s->check.state & CHK_ST_CONFIGURED) {
4641 if (s->check.type == PR_O2_EXT_CHK) {
4642 if (!prepare_external_check(&s->check))
4643 return ERR_ALERT | ERR_FATAL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004644 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004645 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
4646 return ERR_ALERT | ERR_FATAL;
4647 srvpos++;
Christopher Faulet98572322020-03-30 13:16:44 +02004648 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004649
Christopher Faulet61cc8522020-04-20 14:54:42 +02004650 /* A task for a auxiliary agent check */
4651 if (s->agent.state & CHK_ST_CONFIGURED) {
4652 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
4653 return ERR_ALERT | ERR_FATAL;
4654 }
4655 srvpos++;
4656 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004657 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004658 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004659 return 0;
4660}
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004661
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004662
Christopher Faulet61cc8522020-04-20 14:54:42 +02004663/*
4664 * Return value:
4665 * the port to be used for the health check
4666 * 0 in case no port could be found for the check
4667 */
4668static int srv_check_healthcheck_port(struct check *chk)
4669{
4670 int i = 0;
4671 struct server *srv = NULL;
4672
4673 srv = chk->server;
4674
4675 /* by default, we use the health check port ocnfigured */
4676 if (chk->port > 0)
4677 return chk->port;
4678
4679 /* try to get the port from check_core.addr if check.port not set */
4680 i = get_host_port(&chk->addr);
4681 if (i > 0)
4682 return i;
4683
4684 /* try to get the port from server address */
4685 /* prevent MAPPORTS from working at this point, since checks could
4686 * not be performed in such case (MAPPORTS impose a relative ports
4687 * based on live traffic)
4688 */
4689 if (srv->flags & SRV_F_MAPPORTS)
4690 return 0;
4691
4692 i = srv->svc_port; /* by default */
4693 if (i > 0)
4694 return i;
4695
4696 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004697}
4698
Christopher Faulet61cc8522020-04-20 14:54:42 +02004699/* Initializes an health-check attached to the server <srv>. Non-zero is returned
4700 * if an error occurred.
4701 */
4702static int init_srv_check(struct server *srv)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004703{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004704 const char *err;
4705 struct tcpcheck_rule *r;
4706 int ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004707
Christopher Faulet61cc8522020-04-20 14:54:42 +02004708 if (!srv->do_check)
4709 goto out;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004710
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004711
Christopher Faulet61cc8522020-04-20 14:54:42 +02004712 /* If neither a port nor an addr was specified and no check transport
4713 * layer is forced, then the transport layer used by the checks is the
4714 * same as for the production traffic. Otherwise we use raw_sock by
4715 * default, unless one is specified.
4716 */
4717 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4718 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4719 srv->check.use_ssl = srv->use_ssl;
4720 srv->check.xprt = srv->xprt;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004721 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004722 else if (srv->check.use_ssl == 1)
4723 srv->check.xprt = xprt_get(XPRT_SSL);
4724 srv->check.send_proxy |= (srv->pp_opts);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004725 }
4726
Christopher Faulet61cc8522020-04-20 14:54:42 +02004727 /* validate <srv> server health-check settings */
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004728
Christopher Faulet61cc8522020-04-20 14:54:42 +02004729 /* We need at least a service port, a check port or the first tcp-check
4730 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4731 */
4732 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4733 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4734 goto init;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004735
Christopher Faulet61cc8522020-04-20 14:54:42 +02004736 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
4737 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4738 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4739 ret |= ERR_ALERT | ERR_ABORT;
4740 goto out;
4741 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004742
Christopher Faulet61cc8522020-04-20 14:54:42 +02004743 /* search the first action (connect / send / expect) in the list */
4744 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
4745 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
4746 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4747 "nor tcp_check rule 'connect' with port information.\n",
4748 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4749 ret |= ERR_ALERT | ERR_ABORT;
4750 goto out;
4751 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004752
Christopher Faulet61cc8522020-04-20 14:54:42 +02004753 /* scan the tcp-check ruleset to ensure a port has been configured */
4754 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
4755 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
4756 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4757 "and a tcp_check rule 'connect' with no port information.\n",
4758 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4759 ret |= ERR_ALERT | ERR_ABORT;
4760 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004761 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004762 }
4763
Christopher Faulet61cc8522020-04-20 14:54:42 +02004764 init:
4765 if (!(srv->proxy->options2 & PR_O2_CHK_ANY)) {
4766 struct tcpcheck_ruleset *rs = NULL;
4767 struct tcpcheck_rules *rules = &srv->proxy->tcpcheck_rules;
4768 //char *errmsg = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004769
Christopher Faulet61cc8522020-04-20 14:54:42 +02004770 srv->proxy->options2 &= ~PR_O2_CHK_ANY;
4771 srv->proxy->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulete5870d82020-04-15 11:32:03 +02004772
Christopher Faulet61cc8522020-04-20 14:54:42 +02004773 rs = find_tcpcheck_ruleset("*tcp-check");
4774 if (!rs) {
4775 rs = create_tcpcheck_ruleset("*tcp-check");
4776 if (rs == NULL) {
4777 ha_alert("config: %s '%s': out of memory.\n",
4778 proxy_type_str(srv->proxy), srv->proxy->id);
4779 ret |= ERR_ALERT | ERR_FATAL;
4780 goto out;
4781 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004782 }
4783
Christopher Faulet61cc8522020-04-20 14:54:42 +02004784 free_tcpcheck_vars(&rules->preset_vars);
4785 rules->list = &rs->rules;
4786 rules->flags = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004787 }
4788
Christopher Faulet61cc8522020-04-20 14:54:42 +02004789 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4790 if (err) {
4791 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4792 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4793 ret |= ERR_ALERT | ERR_ABORT;
4794 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004795 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004796 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4797 global.maxsock++;
Christopher Faulete5870d82020-04-15 11:32:03 +02004798
Christopher Faulet61cc8522020-04-20 14:54:42 +02004799 out:
4800 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004801}
4802
Christopher Faulet61cc8522020-04-20 14:54:42 +02004803/* Initializes an agent-check attached to the server <srv>. Non-zero is returned
4804 * if an error occurred.
4805 */
4806static int init_srv_agent_check(struct server *srv)
Christopher Faulete5870d82020-04-15 11:32:03 +02004807{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004808 struct tcpcheck_rule *chk;
4809 const char *err;
4810 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004811
Christopher Faulet61cc8522020-04-20 14:54:42 +02004812 if (!srv->do_agent)
4813 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004814
Christopher Faulet61cc8522020-04-20 14:54:42 +02004815 /* If there is no connect rule preceeding all send / expect rules, an
4816 * implicit one is inserted before all others.
4817 */
4818 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4819 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4820 chk = calloc(1, sizeof(*chk));
4821 if (!chk) {
4822 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4823 " to agent-check for server '%s' (out of memory).\n",
4824 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4825 ret |= ERR_ALERT | ERR_FATAL;
4826 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004827 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004828 chk->action = TCPCHK_ACT_CONNECT;
4829 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4830 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
Christopher Faulete5870d82020-04-15 11:32:03 +02004831 }
4832
Christopher Faulete5870d82020-04-15 11:32:03 +02004833
Christopher Faulet61cc8522020-04-20 14:54:42 +02004834 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
4835 if (err) {
4836 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4837 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4838 ret |= ERR_ALERT | ERR_ABORT;
4839 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004840 }
4841
Christopher Faulet61cc8522020-04-20 14:54:42 +02004842 if (!srv->agent.inter)
4843 srv->agent.inter = srv->check.inter;
4844
4845 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4846 global.maxsock++;
4847
4848 out:
4849 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004850}
4851
Christopher Faulet61cc8522020-04-20 14:54:42 +02004852/* Check tcp-check health-check configuration for the proxy <px>. */
4853static int check_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02004854{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004855 struct tcpcheck_rule *chk, *back;
4856 char *comment = NULL, *errmsg = NULL;
4857 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
4858 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004859
Christopher Faulet61cc8522020-04-20 14:54:42 +02004860 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
4861 deinit_proxy_tcpcheck(px);
4862 goto out;
4863 }
4864
4865 free(px->check_command);
4866 free(px->check_path);
4867 px->check_command = px->check_path = NULL;
4868
4869 if (!px->tcpcheck_rules.list) {
4870 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4871 ret |= ERR_ALERT | ERR_FATAL;
4872 goto out;
4873 }
4874
4875 /* HTTP ruleset only : */
4876 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4877 struct tcpcheck_rule *next;
4878
4879 /* move remaining implicit send rule from "option httpchk" line to the right place.
4880 * If such rule exists, it must be the first one. In this case, the rule is moved
4881 * after the first connect rule, if any. Otherwise, nothing is done.
4882 */
4883 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4884 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4885 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
4886 if (next && next->action == TCPCHK_ACT_CONNECT) {
4887 LIST_DEL(&chk->list);
4888 LIST_ADD(&next->list, &chk->list);
4889 chk->index = next->index;
4890 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004891 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004892
4893 /* add implicit expect rule if the last one is a send. It is inherited from previous
4894 * versions where the http expect rule was optional. Now it is possible to chained
4895 * send/expect rules but the last expect may still be implicit.
4896 */
4897 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
4898 if (chk && chk->action == TCPCHK_ACT_SEND) {
4899 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "rstatus", "^[23]", ""},
4900 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
4901 px->conf.file, px->conf.line, &errmsg);
4902 if (!next) {
4903 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
4904 "(%s).\n", px->id, errmsg);
4905 free(errmsg);
4906 ret |= ERR_ALERT | ERR_FATAL;
4907 goto out;
4908 }
4909 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
4910 next->index = chk->index;
Christopher Faulete5870d82020-04-15 11:32:03 +02004911 }
4912 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004913
4914 /* For all ruleset: */
4915
4916 /* If there is no connect rule preceeding all send / expect rules, an
4917 * implicit one is inserted before all others.
4918 */
4919 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4920 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4921 chk = calloc(1, sizeof(*chk));
4922 if (!chk) {
4923 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4924 "(out of memory).\n", px->id);
4925 ret |= ERR_ALERT | ERR_FATAL;
4926 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004927 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004928 chk->action = TCPCHK_ACT_CONNECT;
4929 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4930 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
4931 }
4932
4933 /* Remove all comment rules. To do so, when a such rule is found, the
4934 * comment is assigned to the following rule(s).
4935 */
4936 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4937 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4938 free(comment);
4939 comment = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004940 }
4941
Christopher Faulet61cc8522020-04-20 14:54:42 +02004942 prev_action = chk->action;
4943 switch (chk->action) {
4944 case TCPCHK_ACT_COMMENT:
4945 free(comment);
4946 comment = chk->comment;
4947 LIST_DEL(&chk->list);
4948 free(chk);
4949 break;
4950 case TCPCHK_ACT_CONNECT:
4951 if (!chk->comment && comment)
4952 chk->comment = strdup(comment);
4953 /* fall though */
4954 case TCPCHK_ACT_ACTION_KW:
4955 free(comment);
4956 comment = NULL;
4957 break;
4958 case TCPCHK_ACT_SEND:
4959 case TCPCHK_ACT_EXPECT:
4960 if (!chk->comment && comment)
4961 chk->comment = strdup(comment);
4962 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02004963 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004964 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004965 free(comment);
4966 comment = NULL;
4967
4968 out:
4969 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004970}
4971
Christopher Faulet61cc8522020-04-20 14:54:42 +02004972void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02004973{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004974 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
4975 px->tcpcheck_rules.flags = 0;
4976 px->tcpcheck_rules.list = NULL;
4977}
Christopher Faulete5870d82020-04-15 11:32:03 +02004978
Christopher Faulet61cc8522020-04-20 14:54:42 +02004979static void deinit_srv_check(struct server *srv)
4980{
4981 if (srv->check.state & CHK_ST_CONFIGURED)
4982 free_check(&srv->check);
4983 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
4984 srv->do_check = 0;
4985}
Christopher Faulete5870d82020-04-15 11:32:03 +02004986
Christopher Faulet61cc8522020-04-20 14:54:42 +02004987
4988static void deinit_srv_agent_check(struct server *srv)
4989{
4990 if (srv->agent.tcpcheck_rules) {
4991 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
4992 free(srv->agent.tcpcheck_rules);
4993 srv->agent.tcpcheck_rules = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004994 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004995
Christopher Faulet61cc8522020-04-20 14:54:42 +02004996 if (srv->agent.state & CHK_ST_CONFIGURED)
4997 free_check(&srv->agent);
4998
4999 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
5000 srv->do_agent = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005001}
5002
Christopher Faulet61cc8522020-04-20 14:54:42 +02005003static void deinit_tcpchecks()
Christopher Faulete5870d82020-04-15 11:32:03 +02005004{
Christopher Fauletd7cee712020-04-21 13:45:00 +02005005 struct tcpcheck_ruleset *rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005006 struct tcpcheck_rule *r, *rb;
Christopher Fauletd7cee712020-04-21 13:45:00 +02005007 struct ebpt_node *node, *next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005008
Christopher Fauletd7cee712020-04-21 13:45:00 +02005009 node = ebpt_first(&shared_tcpchecks);
5010 while (node) {
5011 next = ebpt_next(node);
5012 ebpt_delete(node);
5013 free(node->key);
5014 rs = container_of(node, typeof(*rs), node);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005015 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5016 LIST_DEL(&r->list);
5017 free_tcpcheck(r, 0);
5018 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005019 free(rs);
Christopher Fauletd7cee712020-04-21 13:45:00 +02005020 node = next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005021 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005022}
Christopher Faulete5870d82020-04-15 11:32:03 +02005023
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005024
Christopher Faulet61cc8522020-04-20 14:54:42 +02005025REGISTER_POST_SERVER_CHECK(init_srv_check);
5026REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
5027REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5028REGISTER_POST_CHECK(start_checks);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005029
Christopher Faulet61cc8522020-04-20 14:54:42 +02005030REGISTER_SERVER_DEINIT(deinit_srv_check);
5031REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
5032REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5033REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulete5870d82020-04-15 11:32:03 +02005034
Christopher Faulet61cc8522020-04-20 14:54:42 +02005035/**************************************************************************/
5036/****************************** Email alerts ******************************/
5037/* NOTE: It may be pertinent to use an applet to handle email alerts */
5038/* instead of a tcp-check ruleset */
5039/**************************************************************************/
5040void email_alert_free(struct email_alert *alert)
5041{
5042 struct tcpcheck_rule *rule, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02005043
Christopher Faulet61cc8522020-04-20 14:54:42 +02005044 if (!alert)
5045 return;
5046
5047 if (alert->rules.list) {
5048 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
5049 LIST_DEL(&rule->list);
5050 free_tcpcheck(rule, 1);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005051 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005052 free_tcpcheck_vars(&alert->rules.preset_vars);
5053 free(alert->rules.list);
5054 alert->rules.list = NULL;
5055 }
5056 pool_free(pool_head_email_alert, alert);
5057}
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005058
Christopher Faulet61cc8522020-04-20 14:54:42 +02005059static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
5060{
5061 struct check *check = context;
5062 struct email_alertq *q;
5063 struct email_alert *alert;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005064
Christopher Faulet61cc8522020-04-20 14:54:42 +02005065 q = container_of(check, typeof(*q), check);
5066
5067 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5068 while (1) {
5069 if (!(check->state & CHK_ST_ENABLED)) {
5070 if (LIST_ISEMPTY(&q->email_alerts)) {
5071 /* All alerts processed, queue the task */
5072 t->expire = TICK_ETERNITY;
5073 task_queue(t);
5074 goto end;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005075 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005076
5077 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
5078 LIST_DEL(&alert->list);
5079 t->expire = now_ms;
5080 check->tcpcheck_rules = &alert->rules;
5081 check->status = HCHK_STATUS_INI;
5082 check->state |= CHK_ST_ENABLED;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005083 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005084
5085 process_chk(t, context, state);
5086 if (check->state & CHK_ST_INPROGRESS)
5087 break;
5088
5089 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
5090 email_alert_free(alert);
5091 check->tcpcheck_rules = NULL;
5092 check->server = NULL;
5093 check->state &= ~CHK_ST_ENABLED;
5094 }
5095 end:
5096 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5097 return t;
5098}
5099
5100/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
5101 *
5102 * The function returns 1 in success case, otherwise, it returns 0 and err is
5103 * filled.
5104 */
5105int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
5106{
5107 struct mailer *mailer;
5108 struct email_alertq *queues;
5109 const char *err_str;
5110 int i = 0;
5111
5112 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
5113 memprintf(err, "out of memory while allocating mailer alerts queues");
5114 goto fail_no_queue;
5115 }
5116
5117 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
5118 struct email_alertq *q = &queues[i];
5119 struct check *check = &q->check;
5120 struct task *t;
5121
5122 LIST_INIT(&q->email_alerts);
5123 HA_SPIN_INIT(&q->lock);
5124 check->inter = mls->timeout.mail;
5125 check->rise = DEF_AGENT_RISETIME;
5126 check->proxy = p;
5127 check->fall = DEF_AGENT_FALLTIME;
5128 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
5129 memprintf(err, "%s", err_str);
5130 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005131 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005132
5133 check->xprt = mailer->xprt;
5134 check->addr = mailer->addr;
5135 check->port = get_host_port(&mailer->addr);
5136
5137 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5138 memprintf(err, "out of memory while allocating mailer alerts task");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005139 goto error;
5140 }
5141
Christopher Faulet61cc8522020-04-20 14:54:42 +02005142 check->task = t;
5143 t->process = process_email_alert;
5144 t->context = check;
5145
5146 /* check this in one ms */
5147 t->expire = TICK_ETERNITY;
5148 check->start = now;
5149 task_queue(t);
5150 }
5151
5152 mls->users++;
5153 free(p->email_alert.mailers.name);
5154 p->email_alert.mailers.m = mls;
5155 p->email_alert.queues = queues;
5156 return 0;
5157
5158 error:
5159 for (i = 0; i < mls->count; i++) {
5160 struct email_alertq *q = &queues[i];
5161 struct check *check = &q->check;
5162
5163 free_check(check);
5164 }
5165 free(queues);
5166 fail_no_queue:
5167 return 1;
5168}
5169
5170static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
5171{
5172 struct tcpcheck_rule *tcpcheck, *prev_check;
5173 struct tcpcheck_expect *expect;
5174
5175 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5176 return 0;
5177 memset(tcpcheck, 0, sizeof(*tcpcheck));
5178 tcpcheck->action = TCPCHK_ACT_EXPECT;
5179
5180 expect = &tcpcheck->expect;
5181 expect->type = TCPCHK_EXPECT_STRING;
5182 LIST_INIT(&expect->onerror_fmt);
5183 LIST_INIT(&expect->onsuccess_fmt);
5184 expect->ok_status = HCHK_STATUS_L7OKD;
5185 expect->err_status = HCHK_STATUS_L7RSP;
5186 expect->tout_status = HCHK_STATUS_L7TOUT;
5187 expect->data = ist2(strdup(str), strlen(str));
Christopher Fauletb61caf42020-04-21 10:57:42 +02005188 if (!isttest(expect->data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005189 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5190 return 0;
5191 }
5192
5193 /* All tcp-check expect points back to the first inverse expect rule
5194 * in a chain of one or more expect rule, potentially itself.
5195 */
5196 tcpcheck->expect.head = tcpcheck;
5197 list_for_each_entry_rev(prev_check, rules->list, list) {
5198 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5199 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
5200 tcpcheck->expect.head = prev_check;
5201 continue;
5202 }
5203 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
5204 break;
5205 }
5206 LIST_ADDQ(rules->list, &tcpcheck->list);
5207 return 1;
5208}
5209
5210static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
5211{
5212 struct tcpcheck_rule *tcpcheck;
5213 struct tcpcheck_send *send;
5214 const char *in;
5215 char *dst;
5216 int i;
5217
5218 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5219 return 0;
5220 memset(tcpcheck, 0, sizeof(*tcpcheck));
5221 tcpcheck->action = TCPCHK_ACT_SEND;
5222
5223 send = &tcpcheck->send;
5224 send->type = TCPCHK_SEND_STRING;
5225
5226 for (i = 0; strs[i]; i++)
5227 send->data.len += strlen(strs[i]);
5228
Christopher Fauletb61caf42020-04-21 10:57:42 +02005229 send->data.ptr = malloc(istlen(send->data) + 1);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005230 if (!isttest(send->data)) {
5231 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5232 return 0;
5233 }
5234
Christopher Fauletb61caf42020-04-21 10:57:42 +02005235 dst = istptr(send->data);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005236 for (i = 0; strs[i]; i++)
5237 for (in = strs[i]; (*dst = *in++); dst++);
5238 *dst = 0;
5239
5240 LIST_ADDQ(rules->list, &tcpcheck->list);
5241 return 1;
5242}
5243
5244static int enqueue_one_email_alert(struct proxy *p, struct server *s,
5245 struct email_alertq *q, const char *msg)
5246{
5247 struct email_alert *alert;
5248 struct tcpcheck_rule *tcpcheck;
5249 struct check *check = &q->check;
5250
5251 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
5252 goto error;
5253 LIST_INIT(&alert->list);
5254 alert->rules.flags = TCPCHK_RULES_TCP_CHK;
5255 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
5256 if (!alert->rules.list)
5257 goto error;
5258 LIST_INIT(alert->rules.list);
5259 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
5260 alert->srv = s;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005261
Christopher Faulet61cc8522020-04-20 14:54:42 +02005262 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5263 goto error;
5264 memset(tcpcheck, 0, sizeof(*tcpcheck));
5265 tcpcheck->action = TCPCHK_ACT_CONNECT;
5266 tcpcheck->comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005267
Christopher Faulet61cc8522020-04-20 14:54:42 +02005268 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005269
Christopher Faulet61cc8522020-04-20 14:54:42 +02005270 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005271 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005272
5273 {
5274 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
5275 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5276 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005277 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005278
Christopher Faulet61cc8522020-04-20 14:54:42 +02005279 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5280 goto error;
5281
5282 {
5283 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
5284 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005285 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005286 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005287
5288 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5289 goto error;
5290
5291 {
5292 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
5293 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005294 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005295 }
5296
Christopher Faulet61cc8522020-04-20 14:54:42 +02005297 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5298 goto error;
5299
5300 {
5301 const char * const strs[2] = { "DATA\r\n" };
5302 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005303 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005304 }
5305
5306 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
5307 goto error;
5308
5309 {
5310 struct tm tm;
5311 char datestr[48];
5312 const char * const strs[18] = {
5313 "From: ", p->email_alert.from, "\r\n",
5314 "To: ", p->email_alert.to, "\r\n",
5315 "Date: ", datestr, "\r\n",
5316 "Subject: [HAproxy Alert] ", msg, "\r\n",
5317 "\r\n",
5318 msg, "\r\n",
5319 "\r\n",
5320 ".\r\n",
5321 NULL
5322 };
5323
5324 get_localtime(date.tv_sec, &tm);
5325
5326 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005327 goto error;
5328 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005329
5330 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005331 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005332 }
5333
5334 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005335 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005336
5337 {
5338 const char * const strs[2] = { "QUIT\r\n" };
5339 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5340 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005341 }
5342
Christopher Faulet61cc8522020-04-20 14:54:42 +02005343 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
5344 goto error;
5345
5346 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5347 task_wakeup(check->task, TASK_WOKEN_MSG);
5348 LIST_ADDQ(&q->email_alerts, &alert->list);
5349 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5350 return 1;
5351
5352error:
5353 email_alert_free(alert);
5354 return 0;
5355}
5356
5357static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
5358{
5359 int i;
5360 struct mailer *mailer;
5361
5362 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
5363 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
5364 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
5365 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
5366 return;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005367 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005368 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005369
Christopher Faulet61cc8522020-04-20 14:54:42 +02005370 return;
5371}
5372
5373/*
5374 * Send email alert if configured.
5375 */
5376void send_email_alert(struct server *s, int level, const char *format, ...)
5377{
5378 va_list argp;
5379 char buf[1024];
5380 int len;
5381 struct proxy *p = s->proxy;
5382
5383 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
5384 return;
5385
5386 va_start(argp, format);
5387 len = vsnprintf(buf, sizeof(buf), format, argp);
5388 va_end(argp);
5389
5390 if (len < 0 || len >= sizeof(buf)) {
5391 ha_alert("Email alert [%s] could not format message\n", p->id);
5392 return;
5393 }
5394
5395 enqueue_email_alert(p, s, buf);
5396}
5397
5398/**************************************************************************/
5399/************************** Check sample fetches **************************/
5400/**************************************************************************/
5401/* extracts check payload at a fixed position and length */
5402static int
5403smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
5404{
5405 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
5406 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
5407 struct check *check = (smp->sess ? objt_check(smp->sess->origin) : NULL);
5408 struct buffer *buf;
5409
5410 if (!check)
5411 return 0;
5412
5413 buf = &check->bi;
5414 if (buf_offset > b_data(buf))
5415 goto no_match;
5416 if (buf_offset + buf_size > b_data(buf))
5417 buf_size = 0;
5418
5419 /* init chunk as read only */
5420 smp->data.type = SMP_T_STR;
5421 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
5422 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
5423
5424 return 1;
5425
5426 no_match:
5427 smp->flags = 0;
5428 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005429}
5430
Christopher Faulet61cc8522020-04-20 14:54:42 +02005431static struct sample_fetch_kw_list smp_kws = {ILH, {
5432 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
5433 { /* END */ },
5434}};
5435
5436INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
5437
5438
5439/**************************************************************************/
5440/************************ Check's parsing functions ***********************/
5441/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005442/* Parses the "tcp-check" proxy keyword */
5443static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5444 struct proxy *defpx, const char *file, int line,
5445 char **errmsg)
5446{
Christopher Faulet404f9192020-04-09 23:13:54 +02005447 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005448 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005449 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005450
5451 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5452 ret = 1;
5453
Christopher Faulet404f9192020-04-09 23:13:54 +02005454 /* Deduce the ruleset name from the proxy info */
5455 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5456 ((curpx == defpx) ? "defaults" : curpx->id),
5457 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005458
Christopher Faulet61cc8522020-04-20 14:54:42 +02005459 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005460 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005461 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005462 if (rs == NULL) {
5463 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005464 goto error;
5465 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005466 }
5467
Gaetan Rivet5301b012020-02-25 17:19:17 +01005468 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005469 if (!LIST_ISEMPTY(&rs->rules)) {
5470 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005471 index = chk->index + 1;
5472 }
5473
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005474 cur_arg = 1;
5475 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005476 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005477 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005478 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005479 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005480 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005481 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005482 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005483 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005484 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5485
5486 if (!kw) {
5487 action_kw_tcp_check_build_list(&trash);
5488 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5489 "%s%s. but got '%s'",
5490 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5491 goto error;
5492 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005493 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005494 }
5495
5496 if (!chk) {
5497 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5498 goto error;
5499 }
5500 ret = (*errmsg != NULL); /* Handle warning */
5501
5502 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005503 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005504 LIST_ADDQ(&rs->rules, &chk->list);
5505
5506 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +02005507 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005508 /* Use this ruleset if the proxy already has tcp-check enabled */
5509 curpx->tcpcheck_rules.list = &rs->rules;
5510 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5511 }
5512 else {
5513 /* mark this ruleset as unused for now */
5514 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5515 }
5516
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005517 return ret;
5518
5519 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005520 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005521 free_tcpcheck_ruleset(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005522 return -1;
5523}
5524
Christopher Faulet51b129f2020-04-09 15:54:18 +02005525/* Parses the "http-check" proxy keyword */
5526static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5527 struct proxy *defpx, const char *file, int line,
5528 char **errmsg)
5529{
Christopher Faulete5870d82020-04-15 11:32:03 +02005530 struct tcpcheck_ruleset *rs = NULL;
5531 struct tcpcheck_rule *chk = NULL;
5532 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005533
5534 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5535 ret = 1;
5536
5537 cur_arg = 1;
5538 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5539 /* enable a graceful server shutdown on an HTTP 404 response */
5540 curpx->options |= PR_O_DISABLE404;
5541 if (too_many_args(1, args, errmsg, NULL))
5542 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005543 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005544 }
5545 else if (strcmp(args[cur_arg], "send-state") == 0) {
5546 /* enable emission of the apparent state of a server in HTTP checks */
5547 curpx->options2 |= PR_O2_CHK_SNDST;
5548 if (too_many_args(1, args, errmsg, NULL))
5549 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005550 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005551 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005552
Christopher Faulete5870d82020-04-15 11:32:03 +02005553 /* Deduce the ruleset name from the proxy info */
5554 chunk_printf(&trash, "*http-check-%s_%s-%d",
5555 ((curpx == defpx) ? "defaults" : curpx->id),
5556 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005557
Christopher Faulet61cc8522020-04-20 14:54:42 +02005558 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005559 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005560 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005561 if (rs == NULL) {
5562 memprintf(errmsg, "out of memory.\n");
5563 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005564 }
5565 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005566
Christopher Faulete5870d82020-04-15 11:32:03 +02005567 index = 0;
5568 if (!LIST_ISEMPTY(&rs->rules)) {
5569 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5570 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5571 index = chk->index + 1;
5572 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005573
Christopher Faulete5870d82020-04-15 11:32:03 +02005574 if (strcmp(args[cur_arg], "connect") == 0)
5575 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5576 else if (strcmp(args[cur_arg], "send") == 0)
5577 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5578 else if (strcmp(args[cur_arg], "expect") == 0)
5579 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5580 file, line, errmsg);
5581 else if (strcmp(args[cur_arg], "comment") == 0)
5582 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5583 else {
5584 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005585
Christopher Faulete5870d82020-04-15 11:32:03 +02005586 if (!kw) {
5587 action_kw_tcp_check_build_list(&trash);
5588 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5589 " 'send', 'expect'%s%s. but got '%s'",
5590 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5591 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005592 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005593 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5594 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005595
Christopher Faulete5870d82020-04-15 11:32:03 +02005596 if (!chk) {
5597 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5598 goto error;
5599 }
5600 ret = (*errmsg != NULL); /* Handle warning */
5601
5602 chk->index = index;
5603 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5604 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5605 /* Use this ruleset if the proxy already has http-check enabled */
5606 curpx->tcpcheck_rules.list = &rs->rules;
5607 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5608 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5609 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5610 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005611 goto error;
5612 }
5613 }
5614 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005615 /* mark this ruleset as unused for now */
5616 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5617 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005618 }
5619
Christopher Faulete5870d82020-04-15 11:32:03 +02005620 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005621 return ret;
5622
5623 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005624 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005625 free_tcpcheck_ruleset(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005626 return -1;
5627}
5628
Christopher Faulete9111b62020-04-09 18:12:08 +02005629/* Parses the "external-check" proxy keyword */
5630static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5631 struct proxy *defpx, const char *file, int line,
5632 char **errmsg)
5633{
5634 int cur_arg, ret = 0;
5635
5636 cur_arg = 1;
5637 if (!*(args[cur_arg])) {
5638 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5639 goto error;
5640 }
5641
5642 if (strcmp(args[cur_arg], "command") == 0) {
5643 if (too_many_args(2, args, errmsg, NULL))
5644 goto error;
5645 if (!*(args[cur_arg+1])) {
5646 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5647 goto error;
5648 }
5649 free(curpx->check_command);
5650 curpx->check_command = strdup(args[cur_arg+1]);
5651 }
5652 else if (strcmp(args[cur_arg], "path") == 0) {
5653 if (too_many_args(2, args, errmsg, NULL))
5654 goto error;
5655 if (!*(args[cur_arg+1])) {
5656 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5657 goto error;
5658 }
5659 free(curpx->check_path);
5660 curpx->check_path = strdup(args[cur_arg+1]);
5661 }
5662 else {
5663 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5664 args[0], args[1]);
5665 goto error;
5666 }
5667
5668 ret = (*errmsg != NULL); /* Handle warning */
5669 return ret;
5670
5671error:
5672 return -1;
5673}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005674
Christopher Faulet430e4802020-04-09 15:28:16 +02005675/* Parses the "option tcp-check" proxy keyword */
5676int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5677 const char *file, int line)
5678{
Christopher Faulet404f9192020-04-09 23:13:54 +02005679 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005680 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5681 int err_code = 0;
5682
5683 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5684 err_code |= ERR_WARN;
5685
5686 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5687 goto out;
5688
Christopher Faulet404f9192020-04-09 23:13:54 +02005689 curpx->options2 &= ~PR_O2_CHK_ANY;
5690 curpx->options2 |= PR_O2_TCPCHK_CHK;
5691
Christopher Fauletd7e63962020-04-17 20:15:59 +02005692 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005693 /* If a tcp-check rulesset is already set, do nothing */
5694 if (rules->list)
5695 goto out;
5696
5697 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5698 * get it.
5699 */
5700 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5701 goto curpx_ruleset;
5702
5703 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5704 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005705 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005706 if (rs)
5707 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005708 }
5709
Christopher Faulet404f9192020-04-09 23:13:54 +02005710 curpx_ruleset:
5711 /* Deduce the ruleset name from the proxy info */
5712 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5713 ((curpx == defpx) ? "defaults" : curpx->id),
5714 curpx->conf.file, curpx->conf.line);
5715
Christopher Faulet61cc8522020-04-20 14:54:42 +02005716 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005717 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005718 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005719 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005720 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5721 goto error;
5722 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005723 }
5724
Christopher Faulet404f9192020-04-09 23:13:54 +02005725 ruleset_found:
5726 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet404f9192020-04-09 23:13:54 +02005727 rules->list = &rs->rules;
Christopher Fauletd7e63962020-04-17 20:15:59 +02005728 rules->flags |= TCPCHK_RULES_TCP_CHK;
Christopher Faulet430e4802020-04-09 15:28:16 +02005729
5730 out:
5731 return err_code;
5732
5733 error:
5734 err_code |= ERR_ALERT | ERR_FATAL;
5735 goto out;
5736}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005737
5738/* Parses the "option redis-check" proxy keyword */
5739int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5740 const char *file, int line)
5741{
5742 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5743 static char *redis_res = "+PONG\r\n";
5744
5745 struct tcpcheck_ruleset *rs = NULL;
5746 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5747 struct tcpcheck_rule *chk;
5748 char *errmsg = NULL;
5749 int err_code = 0;
5750
5751 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5752 err_code |= ERR_WARN;
5753
5754 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5755 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005756
5757 curpx->options2 &= ~PR_O2_CHK_ANY;
5758 curpx->options2 |= PR_O2_TCPCHK_CHK;
5759
5760 free_tcpcheck_vars(&rules->preset_vars);
5761 rules->list = NULL;
5762 rules->flags = 0;
5763
Christopher Faulet61cc8522020-04-20 14:54:42 +02005764 rs = find_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005765 if (rs)
5766 goto ruleset_found;
5767
Christopher Faulet61cc8522020-04-20 14:54:42 +02005768 rs = create_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005769 if (rs == NULL) {
5770 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5771 goto error;
5772 }
5773
5774 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5775 1, curpx, &rs->rules, file, line, &errmsg);
5776 if (!chk) {
5777 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5778 goto error;
5779 }
5780 chk->index = 0;
5781 LIST_ADDQ(&rs->rules, &chk->list);
5782
5783 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5784 "error-status", "L7STS",
5785 "on-error", "%[check.payload(),cut_crlf]",
5786 "on-success", "Redis server is ok",
5787 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005788 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005789 if (!chk) {
5790 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5791 goto error;
5792 }
5793 chk->index = 1;
5794 LIST_ADDQ(&rs->rules, &chk->list);
5795
Christopher Fauletd7cee712020-04-21 13:45:00 +02005796 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005797
5798 ruleset_found:
5799 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005800 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005801
5802 out:
5803 free(errmsg);
5804 return err_code;
5805
5806 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005807 free_tcpcheck_ruleset(rs);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005808 err_code |= ERR_ALERT | ERR_FATAL;
5809 goto out;
5810}
5811
Christopher Faulet811f78c2020-04-01 11:10:27 +02005812
5813/* Parses the "option ssl-hello-chk" proxy keyword */
5814int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5815 const char *file, int line)
5816{
5817 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5818 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5819 *
5820 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5821 */
5822 static char sslv3_client_hello[] = {
5823 "16" /* ContentType : 0x16 = Hanshake */
5824 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5825 "0079" /* ContentLength : 0x79 bytes after this one */
5826 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5827 "000075" /* HandshakeLength : 0x75 bytes after this one */
5828 "0300" /* Hello Version : 0x0300 = v3 */
5829 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5830 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5831 "00" /* Session ID length : empty (no session ID) */
5832 "004E" /* Cipher Suite Length : 78 bytes after this one */
5833 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5834 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5835 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5836 "000D" "000E" "000F" "0010" /* various bit lengths, */
5837 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5838 "0015" "0016" "0017" "0018"
5839 "0019" "001A" "001B" "002F"
5840 "0030" "0031" "0032" "0033"
5841 "0034" "0035" "0036" "0037"
5842 "0038" "0039" "003A"
5843 "01" /* Compression Length : 0x01 = 1 byte for types */
5844 "00" /* Compression Type : 0x00 = NULL compression */
5845 };
5846
5847 struct tcpcheck_ruleset *rs = NULL;
5848 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5849 struct tcpcheck_rule *chk;
5850 char *errmsg = NULL;
5851 int err_code = 0;
5852
5853 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5854 err_code |= ERR_WARN;
5855
5856 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5857 goto out;
5858
Christopher Faulet811f78c2020-04-01 11:10:27 +02005859 curpx->options2 &= ~PR_O2_CHK_ANY;
5860 curpx->options2 |= PR_O2_TCPCHK_CHK;
5861
5862 free_tcpcheck_vars(&rules->preset_vars);
5863 rules->list = NULL;
5864 rules->flags = 0;
5865
Christopher Faulet61cc8522020-04-20 14:54:42 +02005866 rs = find_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005867 if (rs)
5868 goto ruleset_found;
5869
Christopher Faulet61cc8522020-04-20 14:54:42 +02005870 rs = create_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005871 if (rs == NULL) {
5872 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5873 goto error;
5874 }
5875
5876 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5877 1, curpx, &rs->rules, file, line, &errmsg);
5878 if (!chk) {
5879 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5880 goto error;
5881 }
5882 chk->index = 0;
5883 LIST_ADDQ(&rs->rules, &chk->list);
5884
5885 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005886 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005887 "error-status", "L6RSP", "tout-status", "L6TOUT",
5888 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005889 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005890 if (!chk) {
5891 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5892 goto error;
5893 }
5894 chk->index = 1;
5895 LIST_ADDQ(&rs->rules, &chk->list);
5896
Christopher Fauletd7cee712020-04-21 13:45:00 +02005897 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005898
5899 ruleset_found:
5900 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005901 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005902
5903 out:
5904 free(errmsg);
5905 return err_code;
5906
5907 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005908 free_tcpcheck_ruleset(rs);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005909 err_code |= ERR_ALERT | ERR_FATAL;
5910 goto out;
5911}
5912
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005913/* Parses the "option smtpchk" proxy keyword */
5914int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5915 const char *file, int line)
5916{
5917 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5918
5919 struct tcpcheck_ruleset *rs = NULL;
5920 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5921 struct tcpcheck_rule *chk;
5922 struct tcpcheck_var *var = NULL;
5923 char *cmd = NULL, *errmsg = NULL;
5924 int err_code = 0;
5925
5926 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5927 err_code |= ERR_WARN;
5928
5929 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5930 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005931
5932 curpx->options2 &= ~PR_O2_CHK_ANY;
5933 curpx->options2 |= PR_O2_TCPCHK_CHK;
5934
5935 free_tcpcheck_vars(&rules->preset_vars);
5936 rules->list = NULL;
5937 rules->flags = 0;
5938
5939 cur_arg += 2;
5940 if (*args[cur_arg] && *args[cur_arg+1] &&
5941 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5942 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5943 if (cmd)
5944 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
5945 }
5946 else {
5947 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
5948 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
5949 cmd = strdup("HELO localhost");
5950 }
5951
Christopher Fauletb61caf42020-04-21 10:57:42 +02005952 var = create_tcpcheck_var(ist("check.smtp_cmd"));
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005953 if (cmd == NULL || var == NULL) {
5954 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5955 goto error;
5956 }
5957 var->data.type = SMP_T_STR;
5958 var->data.u.str.area = cmd;
5959 var->data.u.str.data = strlen(cmd);
5960 LIST_INIT(&var->list);
5961 LIST_ADDQ(&rules->preset_vars, &var->list);
5962 cmd = NULL;
5963 var = NULL;
5964
Christopher Faulet61cc8522020-04-20 14:54:42 +02005965 rs = find_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005966 if (rs)
5967 goto ruleset_found;
5968
Christopher Faulet61cc8522020-04-20 14:54:42 +02005969 rs = create_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005970 if (rs == NULL) {
5971 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5972 goto error;
5973 }
5974
5975 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5976 1, curpx, &rs->rules, file, line, &errmsg);
5977 if (!chk) {
5978 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5979 goto error;
5980 }
5981 chk->index = 0;
5982 LIST_ADDQ(&rs->rules, &chk->list);
5983
5984 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
5985 "min-recv", "4",
5986 "error-status", "L7RSP",
5987 "on-error", "%[check.payload(),cut_crlf]",
5988 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005989 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005990 if (!chk) {
5991 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5992 goto error;
5993 }
5994 chk->index = 1;
5995 LIST_ADDQ(&rs->rules, &chk->list);
5996
5997 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
5998 "min-recv", "4",
5999 "error-status", "L7STS",
6000 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6001 "status-code", "check.payload(0,3)",
6002 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006003 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006004 if (!chk) {
6005 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6006 goto error;
6007 }
6008 chk->index = 2;
6009 LIST_ADDQ(&rs->rules, &chk->list);
6010
6011 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6012 1, curpx, &rs->rules, file, line, &errmsg);
6013 if (!chk) {
6014 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6015 goto error;
6016 }
6017 chk->index = 3;
6018 LIST_ADDQ(&rs->rules, &chk->list);
6019
6020 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6021 "min-recv", "4",
6022 "error-status", "L7STS",
6023 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6024 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6025 "status-code", "check.payload(0,3)",
6026 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006027 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006028 if (!chk) {
6029 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6030 goto error;
6031 }
6032 chk->index = 4;
6033 LIST_ADDQ(&rs->rules, &chk->list);
6034
Christopher Fauletd7cee712020-04-21 13:45:00 +02006035 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006036
6037 ruleset_found:
6038 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006039 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006040
6041 out:
6042 free(errmsg);
6043 return err_code;
6044
6045 error:
6046 free(cmd);
6047 free(var);
6048 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006049 free_tcpcheck_ruleset(rs);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006050 err_code |= ERR_ALERT | ERR_FATAL;
6051 goto out;
6052}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006053
Christopher Fauletce355072020-04-02 11:44:39 +02006054/* Parses the "option pgsql-check" proxy keyword */
6055int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6056 const char *file, int line)
6057{
6058 static char pgsql_req[] = {
6059 "%[var(check.plen),htonl,hex]" /* The packet length*/
6060 "00030000" /* the version 3.0 */
6061 "7573657200" /* "user" key */
6062 "%[var(check.username),hex]00" /* the username */
6063 "00"
6064 };
6065
6066 struct tcpcheck_ruleset *rs = NULL;
6067 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6068 struct tcpcheck_rule *chk;
6069 struct tcpcheck_var *var = NULL;
6070 char *user = NULL, *errmsg = NULL;
6071 size_t packetlen = 0;
6072 int err_code = 0;
6073
6074 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6075 err_code |= ERR_WARN;
6076
6077 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6078 goto out;
6079
Christopher Fauletce355072020-04-02 11:44:39 +02006080 curpx->options2 &= ~PR_O2_CHK_ANY;
6081 curpx->options2 |= PR_O2_TCPCHK_CHK;
6082
6083 free_tcpcheck_vars(&rules->preset_vars);
6084 rules->list = NULL;
6085 rules->flags = 0;
6086
6087 cur_arg += 2;
6088 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6089 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6090 file, line, args[0], args[1]);
6091 goto error;
6092 }
6093 if (strcmp(args[cur_arg], "user") == 0) {
6094 packetlen = 15 + strlen(args[cur_arg+1]);
6095 user = strdup(args[cur_arg+1]);
6096
Christopher Fauletb61caf42020-04-21 10:57:42 +02006097 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletce355072020-04-02 11:44:39 +02006098 if (user == NULL || var == NULL) {
6099 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6100 goto error;
6101 }
6102 var->data.type = SMP_T_STR;
6103 var->data.u.str.area = user;
6104 var->data.u.str.data = strlen(user);
6105 LIST_INIT(&var->list);
6106 LIST_ADDQ(&rules->preset_vars, &var->list);
6107 user = NULL;
6108 var = NULL;
6109
Christopher Fauletb61caf42020-04-21 10:57:42 +02006110 var = create_tcpcheck_var(ist("check.plen"));
Christopher Fauletce355072020-04-02 11:44:39 +02006111 if (var == NULL) {
6112 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6113 goto error;
6114 }
6115 var->data.type = SMP_T_SINT;
6116 var->data.u.sint = packetlen;
6117 LIST_INIT(&var->list);
6118 LIST_ADDQ(&rules->preset_vars, &var->list);
6119 var = NULL;
6120 }
6121 else {
6122 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6123 file, line, args[0], args[1]);
6124 goto error;
6125 }
6126
Christopher Faulet61cc8522020-04-20 14:54:42 +02006127 rs = find_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006128 if (rs)
6129 goto ruleset_found;
6130
Christopher Faulet61cc8522020-04-20 14:54:42 +02006131 rs = create_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006132 if (rs == NULL) {
6133 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6134 goto error;
6135 }
6136
6137 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6138 1, curpx, &rs->rules, file, line, &errmsg);
6139 if (!chk) {
6140 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6141 goto error;
6142 }
6143 chk->index = 0;
6144 LIST_ADDQ(&rs->rules, &chk->list);
6145
6146 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
6147 1, curpx, &rs->rules, file, line, &errmsg);
6148 if (!chk) {
6149 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6150 goto error;
6151 }
6152 chk->index = 1;
6153 LIST_ADDQ(&rs->rules, &chk->list);
6154
6155 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6156 "min-recv", "5",
6157 "error-status", "L7RSP",
6158 "on-error", "%[check.payload(6,0)]",
6159 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006160 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006161 if (!chk) {
6162 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6163 goto error;
6164 }
6165 chk->index = 2;
6166 LIST_ADDQ(&rs->rules, &chk->list);
6167
6168 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
6169 "min-recv", "9",
6170 "error-status", "L7STS",
6171 "on-success", "PostgreSQL server is ok",
6172 "on-error", "PostgreSQL unknown error",
6173 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006174 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006175 if (!chk) {
6176 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6177 goto error;
6178 }
6179 chk->index = 3;
6180 LIST_ADDQ(&rs->rules, &chk->list);
6181
Christopher Fauletd7cee712020-04-21 13:45:00 +02006182 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletce355072020-04-02 11:44:39 +02006183
6184 ruleset_found:
6185 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006186 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006187
6188 out:
6189 free(errmsg);
6190 return err_code;
6191
6192 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006193 free(user);
6194 free(var);
6195 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006196 free_tcpcheck_ruleset(rs);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006197 err_code |= ERR_ALERT | ERR_FATAL;
6198 goto out;
6199}
6200
6201
6202/* Parses the "option mysql-check" proxy keyword */
6203int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6204 const char *file, int line)
6205{
6206 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6207 * const char mysql40_client_auth_pkt[] = {
6208 * "\x0e\x00\x00" // packet length
6209 * "\x01" // packet number
6210 * "\x00\x00" // client capabilities
6211 * "\x00\x00\x01" // max packet
6212 * "haproxy\x00" // username (null terminated string)
6213 * "\x00" // filler (always 0x00)
6214 * "\x01\x00\x00" // packet length
6215 * "\x00" // packet number
6216 * "\x01" // COM_QUIT command
6217 * };
6218 */
6219 static char mysql40_rsname[] = "*mysql40-check";
6220 static char mysql40_req[] = {
6221 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6222 "0080" /* client capabilities */
6223 "000001" /* max packet */
6224 "%[var(check.username),hex]00" /* the username */
6225 "00" /* filler (always 0x00) */
6226 "010000" /* packet length*/
6227 "00" /* sequence ID */
6228 "01" /* COM_QUIT command */
6229 };
6230
6231 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6232 * const char mysql41_client_auth_pkt[] = {
6233 * "\x0e\x00\x00\" // packet length
6234 * "\x01" // packet number
6235 * "\x00\x00\x00\x00" // client capabilities
6236 * "\x00\x00\x00\x01" // max packet
6237 * "\x21" // character set (UTF-8)
6238 * char[23] // All zeroes
6239 * "haproxy\x00" // username (null terminated string)
6240 * "\x00" // filler (always 0x00)
6241 * "\x01\x00\x00" // packet length
6242 * "\x00" // packet number
6243 * "\x01" // COM_QUIT command
6244 * };
6245 */
6246 static char mysql41_rsname[] = "*mysql41-check";
6247 static char mysql41_req[] = {
6248 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6249 "00820000" /* client capabilities */
6250 "00800001" /* max packet */
6251 "21" /* character set (UTF-8) */
6252 "000000000000000000000000" /* 23 bytes, al zeroes */
6253 "0000000000000000000000"
6254 "%[var(check.username),hex]00" /* the username */
6255 "00" /* filler (always 0x00) */
6256 "010000" /* packet length*/
6257 "00" /* sequence ID */
6258 "01" /* COM_QUIT command */
6259 };
6260
6261 struct tcpcheck_ruleset *rs = NULL;
6262 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6263 struct tcpcheck_rule *chk;
6264 struct tcpcheck_var *var = NULL;
6265 char *mysql_rsname = "*mysql-check";
6266 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6267 int index = 0, err_code = 0;
6268
6269 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6270 err_code |= ERR_WARN;
6271
6272 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6273 goto out;
6274
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006275 curpx->options2 &= ~PR_O2_CHK_ANY;
6276 curpx->options2 |= PR_O2_TCPCHK_CHK;
6277
6278 free_tcpcheck_vars(&rules->preset_vars);
6279 rules->list = NULL;
6280 rules->flags = 0;
6281
6282 cur_arg += 2;
6283 if (*args[cur_arg]) {
6284 char *user;
6285 int packetlen, userlen;
6286
6287 if (strcmp(args[cur_arg], "user") != 0) {
6288 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6289 file, line, args[0], args[1], args[cur_arg]);
6290 goto error;
6291 }
6292
6293 if (*(args[cur_arg+1]) == 0) {
6294 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6295 file, line, args[0], args[1], args[cur_arg]);
6296 goto error;
6297 }
6298
6299 hdr = calloc(4, sizeof(*hdr));
6300 user = strdup(args[cur_arg+1]);
6301 userlen = strlen(args[cur_arg+1]);
6302
6303 if (hdr == NULL || user == NULL) {
6304 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6305 goto error;
6306 }
6307
6308 if (*args[cur_arg+2]) {
6309 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6310 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6311 file, line, args[cur_arg], args[cur_arg+2]);
6312 goto error;
6313 }
6314 packetlen = userlen + 7 + 27;
6315 mysql_req = mysql41_req;
6316 mysql_rsname = mysql41_rsname;
6317 }
6318 else {
6319 packetlen = userlen + 7;
6320 mysql_req = mysql40_req;
6321 mysql_rsname = mysql40_rsname;
6322 }
6323
6324 hdr[0] = (unsigned char)(packetlen & 0xff);
6325 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6326 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6327 hdr[3] = 1;
6328
Christopher Fauletb61caf42020-04-21 10:57:42 +02006329 var = create_tcpcheck_var(ist("check.header"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006330 if (var == NULL) {
6331 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6332 goto error;
6333 }
6334 var->data.type = SMP_T_STR;
6335 var->data.u.str.area = hdr;
6336 var->data.u.str.data = 4;
6337 LIST_INIT(&var->list);
6338 LIST_ADDQ(&rules->preset_vars, &var->list);
6339 hdr = NULL;
6340 var = NULL;
6341
Christopher Fauletb61caf42020-04-21 10:57:42 +02006342 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006343 if (var == NULL) {
6344 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6345 goto error;
6346 }
6347 var->data.type = SMP_T_STR;
6348 var->data.u.str.area = user;
6349 var->data.u.str.data = strlen(user);
6350 LIST_INIT(&var->list);
6351 LIST_ADDQ(&rules->preset_vars, &var->list);
6352 user = NULL;
6353 var = NULL;
6354 }
6355
Christopher Faulet61cc8522020-04-20 14:54:42 +02006356 rs = find_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006357 if (rs)
6358 goto ruleset_found;
6359
Christopher Faulet61cc8522020-04-20 14:54:42 +02006360 rs = create_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006361 if (rs == NULL) {
6362 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6363 goto error;
6364 }
6365
6366 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6367 1, curpx, &rs->rules, file, line, &errmsg);
6368 if (!chk) {
6369 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6370 goto error;
6371 }
6372 chk->index = index++;
6373 LIST_ADDQ(&rs->rules, &chk->list);
6374
6375 if (mysql_req) {
6376 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6377 1, curpx, &rs->rules, file, line, &errmsg);
6378 if (!chk) {
6379 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6380 goto error;
6381 }
6382 chk->index = index++;
6383 LIST_ADDQ(&rs->rules, &chk->list);
6384 }
6385
6386 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006387 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006388 if (!chk) {
6389 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6390 goto error;
6391 }
6392 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6393 chk->index = index++;
6394 LIST_ADDQ(&rs->rules, &chk->list);
6395
6396 if (mysql_req) {
6397 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006398 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006399 if (!chk) {
6400 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6401 goto error;
6402 }
6403 chk->expect.custom = tcpcheck_mysql_expect_ok;
6404 chk->index = index++;
6405 LIST_ADDQ(&rs->rules, &chk->list);
6406 }
6407
Christopher Fauletd7cee712020-04-21 13:45:00 +02006408 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006409
6410 ruleset_found:
6411 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006412 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006413
6414 out:
6415 free(errmsg);
6416 return err_code;
6417
6418 error:
6419 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006420 free(user);
6421 free(var);
6422 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006423 free_tcpcheck_ruleset(rs);
Christopher Fauletce355072020-04-02 11:44:39 +02006424 err_code |= ERR_ALERT | ERR_FATAL;
6425 goto out;
6426}
6427
Christopher Faulet1997eca2020-04-03 23:13:50 +02006428int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6429 const char *file, int line)
6430{
6431 static char *ldap_req = "300C020101600702010304008000";
6432
6433 struct tcpcheck_ruleset *rs = NULL;
6434 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6435 struct tcpcheck_rule *chk;
6436 char *errmsg = NULL;
6437 int err_code = 0;
6438
6439 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6440 err_code |= ERR_WARN;
6441
6442 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6443 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006444
6445 curpx->options2 &= ~PR_O2_CHK_ANY;
6446 curpx->options2 |= PR_O2_TCPCHK_CHK;
6447
6448 free_tcpcheck_vars(&rules->preset_vars);
6449 rules->list = NULL;
6450 rules->flags = 0;
6451
Christopher Faulet61cc8522020-04-20 14:54:42 +02006452 rs = find_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006453 if (rs)
6454 goto ruleset_found;
6455
Christopher Faulet61cc8522020-04-20 14:54:42 +02006456 rs = create_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006457 if (rs == NULL) {
6458 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6459 goto error;
6460 }
6461
6462 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6463 1, curpx, &rs->rules, file, line, &errmsg);
6464 if (!chk) {
6465 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6466 goto error;
6467 }
6468 chk->index = 0;
6469 LIST_ADDQ(&rs->rules, &chk->list);
6470
6471 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6472 "min-recv", "14",
6473 "on-error", "Not LDAPv3 protocol",
6474 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006475 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006476 if (!chk) {
6477 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6478 goto error;
6479 }
6480 chk->index = 1;
6481 LIST_ADDQ(&rs->rules, &chk->list);
6482
6483 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006484 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006485 if (!chk) {
6486 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6487 goto error;
6488 }
6489 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6490 chk->index = 2;
6491 LIST_ADDQ(&rs->rules, &chk->list);
6492
Christopher Fauletd7cee712020-04-21 13:45:00 +02006493 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006494
6495 ruleset_found:
6496 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006497 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006498
6499 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006500 free(errmsg);
6501 return err_code;
6502
6503 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006504 free_tcpcheck_ruleset(rs);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006505 err_code |= ERR_ALERT | ERR_FATAL;
6506 goto out;
6507}
6508
6509int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6510 const char *file, int line)
6511{
6512 struct tcpcheck_ruleset *rs = NULL;
6513 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6514 struct tcpcheck_rule *chk;
6515 char *spop_req = NULL;
6516 char *errmsg = NULL;
6517 int spop_len = 0, err_code = 0;
6518
6519 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6520 err_code |= ERR_WARN;
6521
6522 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6523 goto out;
6524
Christopher Faulet267b01b2020-04-04 10:27:09 +02006525 curpx->options2 &= ~PR_O2_CHK_ANY;
6526 curpx->options2 |= PR_O2_TCPCHK_CHK;
6527
6528 free_tcpcheck_vars(&rules->preset_vars);
6529 rules->list = NULL;
6530 rules->flags = 0;
6531
6532
Christopher Faulet61cc8522020-04-20 14:54:42 +02006533 rs = find_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006534 if (rs)
6535 goto ruleset_found;
6536
Christopher Faulet61cc8522020-04-20 14:54:42 +02006537 rs = create_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006538 if (rs == NULL) {
6539 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6540 goto error;
6541 }
6542
6543 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6544 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6545 goto error;
6546 }
6547 chunk_reset(&trash);
6548 dump_binary(&trash, spop_req, spop_len);
6549 trash.area[trash.data] = '\0';
6550
6551 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6552 1, curpx, &rs->rules, file, line, &errmsg);
6553 if (!chk) {
6554 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6555 goto error;
6556 }
6557 chk->index = 0;
6558 LIST_ADDQ(&rs->rules, &chk->list);
6559
6560 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006561 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006562 if (!chk) {
6563 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6564 goto error;
6565 }
6566 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6567 chk->index = 1;
6568 LIST_ADDQ(&rs->rules, &chk->list);
6569
Christopher Fauletd7cee712020-04-21 13:45:00 +02006570 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006571
6572 ruleset_found:
6573 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006574 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006575
6576 out:
6577 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006578 free(errmsg);
6579 return err_code;
6580
6581 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006582 free_tcpcheck_ruleset(rs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006583 err_code |= ERR_ALERT | ERR_FATAL;
6584 goto out;
6585}
Christopher Fauletce355072020-04-02 11:44:39 +02006586
Christopher Faulete5870d82020-04-15 11:32:03 +02006587
6588struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6589{
6590 struct tcpcheck_rule *chk = NULL;
6591 struct tcpcheck_http_hdr *hdr = NULL;
6592 char *meth = NULL, *uri = NULL, *vsn = NULL;
6593 char *hdrs, *body;
6594
6595 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6596 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6597 if (hdrs == body)
6598 hdrs = NULL;
6599 if (hdrs) {
6600 *hdrs = '\0';
6601 hdrs +=2;
6602 }
6603 if (body) {
6604 *body = '\0';
6605 body += 4;
6606 }
6607 if (hdrs || body) {
6608 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6609 " Please, consider to use 'http-check send' directive instead.");
6610 }
6611
6612 chk = calloc(1, sizeof(*chk));
6613 if (!chk) {
6614 memprintf(errmsg, "out of memory");
6615 goto error;
6616 }
6617 chk->action = TCPCHK_ACT_SEND;
6618 chk->send.type = TCPCHK_SEND_HTTP;
6619 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6620 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6621 LIST_INIT(&chk->send.http.hdrs);
6622
6623 /* Copy the method, uri and version */
6624 if (*args[cur_arg]) {
6625 if (!*args[cur_arg+1])
6626 uri = args[cur_arg];
6627 else
6628 meth = args[cur_arg];
6629 }
6630 if (*args[cur_arg+1])
6631 uri = args[cur_arg+1];
6632 if (*args[cur_arg+2])
6633 vsn = args[cur_arg+2];
6634
6635 if (meth) {
6636 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6637 chk->send.http.meth.str.area = strdup(meth);
6638 chk->send.http.meth.str.data = strlen(meth);
6639 if (!chk->send.http.meth.str.area) {
6640 memprintf(errmsg, "out of memory");
6641 goto error;
6642 }
6643 }
6644 if (uri) {
6645 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006646 if (!isttest(chk->send.http.uri)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006647 memprintf(errmsg, "out of memory");
6648 goto error;
6649 }
6650 }
6651 if (vsn) {
6652 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006653 if (!isttest(chk->send.http.vsn)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006654 memprintf(errmsg, "out of memory");
6655 goto error;
6656 }
6657 }
6658
6659 /* Copy the header */
6660 if (hdrs) {
6661 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
6662 struct h1m h1m;
6663 int i, ret;
6664
6665 /* Build and parse the request */
6666 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
6667
6668 h1m.flags = H1_MF_HDRS_ONLY;
6669 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
6670 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
6671 &h1m, NULL);
6672 if (ret <= 0) {
6673 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
6674 goto error;
6675 }
6676
Christopher Fauletb61caf42020-04-21 10:57:42 +02006677 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006678 hdr = calloc(1, sizeof(*hdr));
6679 if (!hdr) {
6680 memprintf(errmsg, "out of memory");
6681 goto error;
6682 }
6683 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02006684 hdr->name = istdup(tmp_hdrs[i].n);
Christopher Faulete5870d82020-04-15 11:32:03 +02006685 if (!hdr->name.ptr) {
6686 memprintf(errmsg, "out of memory");
6687 goto error;
6688 }
6689
Christopher Fauletb61caf42020-04-21 10:57:42 +02006690 ist0(tmp_hdrs[i].v);
6691 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 +02006692 goto error;
6693 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
6694 }
6695 }
6696
6697 /* Copy the body */
6698 if (body) {
6699 chk->send.http.body = ist2(strdup(body), strlen(body));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006700 if (!isttest(chk->send.http.body)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006701 memprintf(errmsg, "out of memory");
6702 goto error;
6703 }
6704 }
6705
6706 return chk;
6707
6708 error:
6709 free_tcpcheck_http_hdr(hdr);
6710 free_tcpcheck(chk, 0);
6711 return NULL;
6712}
6713
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006714int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6715 const char *file, int line)
6716{
Christopher Faulete5870d82020-04-15 11:32:03 +02006717 struct tcpcheck_ruleset *rs = NULL;
6718 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6719 struct tcpcheck_rule *chk;
6720 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006721 int err_code = 0;
6722
6723 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6724 err_code |= ERR_WARN;
6725
6726 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6727 goto out;
6728
Christopher Faulete5870d82020-04-15 11:32:03 +02006729 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
6730 if (!chk) {
6731 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6732 goto error;
6733 }
6734 if (errmsg) {
6735 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
6736 err_code |= ERR_WARN;
6737 free(errmsg);
6738 errmsg = NULL;
6739 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006740
Christopher Faulete5870d82020-04-15 11:32:03 +02006741 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006742 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02006743 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006744
Christopher Faulete5870d82020-04-15 11:32:03 +02006745 free_tcpcheck_vars(&rules->preset_vars);
6746 rules->list = NULL;
6747 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006748
Christopher Faulete5870d82020-04-15 11:32:03 +02006749 /* Deduce the ruleset name from the proxy info */
6750 chunk_printf(&trash, "*http-check-%s_%s-%d",
6751 ((curpx == defpx) ? "defaults" : curpx->id),
6752 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006753
Christopher Faulet61cc8522020-04-20 14:54:42 +02006754 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006755 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006756 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006757 if (rs == NULL) {
6758 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6759 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006760 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006761 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006762
Christopher Faulete5870d82020-04-15 11:32:03 +02006763 rules->list = &rs->rules;
6764 rules->flags |= TCPCHK_RULES_HTTP_CHK;
6765 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
6766 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6767 rules->list = NULL;
6768 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006769 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006770
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006771 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02006772 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006773 return err_code;
6774
6775 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006776 free_tcpcheck_ruleset(rs);
Christopher Faulete5870d82020-04-15 11:32:03 +02006777 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006778 err_code |= ERR_ALERT | ERR_FATAL;
6779 goto out;
6780}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006781
Christopher Faulet6f557912020-04-09 15:58:50 +02006782int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6783 const char *file, int line)
6784{
6785 int err_code = 0;
6786
Christopher Faulet6f557912020-04-09 15:58:50 +02006787 curpx->options2 &= ~PR_O2_CHK_ANY;
6788 curpx->options2 |= PR_O2_EXT_CHK;
6789 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6790 goto out;
6791
6792 out:
6793 return err_code;
6794}
6795
Christopher Fauletce8111e2020-04-06 15:04:11 +02006796/* Parse the "addr" server keyword */
6797static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6798 char **errmsg)
6799{
6800 struct sockaddr_storage *sk;
6801 struct protocol *proto;
6802 int port1, port2, err_code = 0;
6803
6804
6805 if (!*args[*cur_arg+1]) {
6806 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6807 goto error;
6808 }
6809
6810 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6811 if (!sk) {
6812 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6813 goto error;
6814 }
6815
6816 proto = protocol_by_family(sk->ss_family);
6817 if (!proto || !proto->connect) {
6818 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6819 args[*cur_arg], args[*cur_arg+1]);
6820 goto error;
6821 }
6822
6823 if (port1 != port2) {
6824 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6825 args[*cur_arg], args[*cur_arg+1]);
6826 goto error;
6827 }
6828
6829 srv->check.addr = srv->agent.addr = *sk;
6830 srv->flags |= SRV_F_CHECKADDR;
6831 srv->flags |= SRV_F_AGENTADDR;
6832
6833 out:
6834 return err_code;
6835
6836 error:
6837 err_code |= ERR_ALERT | ERR_FATAL;
6838 goto out;
6839}
6840
6841
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006842/* Parse the "agent-addr" server keyword */
6843static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6844 char **errmsg)
6845{
6846 int err_code = 0;
6847
6848 if (!*(args[*cur_arg+1])) {
6849 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6850 goto error;
6851 }
6852 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6853 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6854 goto error;
6855 }
6856
6857 out:
6858 return err_code;
6859
6860 error:
6861 err_code |= ERR_ALERT | ERR_FATAL;
6862 goto out;
6863}
6864
6865/* Parse the "agent-check" server keyword */
6866static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6867 char **errmsg)
6868{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006869 struct tcpcheck_ruleset *rs = NULL;
6870 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6871 struct tcpcheck_rule *chk;
6872 int err_code = 0;
6873
6874 if (srv->do_agent)
6875 goto out;
6876
6877 if (!rules) {
6878 rules = calloc(1, sizeof(*rules));
6879 if (!rules) {
6880 memprintf(errmsg, "out of memory.");
6881 goto error;
6882 }
6883 LIST_INIT(&rules->preset_vars);
6884 srv->agent.tcpcheck_rules = rules;
6885 }
6886 rules->list = NULL;
6887 rules->flags = 0;
6888
Christopher Faulet61cc8522020-04-20 14:54:42 +02006889 rs = find_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006890 if (rs)
6891 goto ruleset_found;
6892
Christopher Faulet61cc8522020-04-20 14:54:42 +02006893 rs = create_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006894 if (rs == NULL) {
6895 memprintf(errmsg, "out of memory.");
6896 goto error;
6897 }
6898
6899 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6900 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6901 if (!chk) {
6902 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6903 goto error;
6904 }
6905 chk->index = 0;
6906 LIST_ADDQ(&rs->rules, &chk->list);
6907
6908 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006909 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
6910 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006911 if (!chk) {
6912 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6913 goto error;
6914 }
6915 chk->expect.custom = tcpcheck_agent_expect_reply;
6916 chk->index = 1;
6917 LIST_ADDQ(&rs->rules, &chk->list);
6918
Christopher Fauletd7cee712020-04-21 13:45:00 +02006919 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006920
6921 ruleset_found:
6922 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006923 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006924 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006925
6926 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006927 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006928
6929 error:
6930 deinit_srv_agent_check(srv);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006931 free_tcpcheck_ruleset(rs);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006932 err_code |= ERR_ALERT | ERR_FATAL;
6933 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006934}
6935
6936/* Parse the "agent-inter" server keyword */
6937static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6938 char **errmsg)
6939{
6940 const char *err = NULL;
6941 unsigned int delay;
6942 int err_code = 0;
6943
6944 if (!*(args[*cur_arg+1])) {
6945 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6946 goto error;
6947 }
6948
6949 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6950 if (err == PARSE_TIME_OVER) {
6951 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6952 args[*cur_arg+1], args[*cur_arg], srv->id);
6953 goto error;
6954 }
6955 else if (err == PARSE_TIME_UNDER) {
6956 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6957 args[*cur_arg+1], args[*cur_arg], srv->id);
6958 goto error;
6959 }
6960 else if (err) {
6961 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6962 *err, srv->id);
6963 goto error;
6964 }
6965 if (delay <= 0) {
6966 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6967 delay, args[*cur_arg], srv->id);
6968 goto error;
6969 }
6970 srv->agent.inter = delay;
6971
6972 out:
6973 return err_code;
6974
6975 error:
6976 err_code |= ERR_ALERT | ERR_FATAL;
6977 goto out;
6978}
6979
6980/* Parse the "agent-port" server keyword */
6981static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6982 char **errmsg)
6983{
6984 int err_code = 0;
6985
6986 if (!*(args[*cur_arg+1])) {
6987 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6988 goto error;
6989 }
6990
6991 global.maxsock++;
6992 srv->agent.port = atol(args[*cur_arg+1]);
6993
6994 out:
6995 return err_code;
6996
6997 error:
6998 err_code |= ERR_ALERT | ERR_FATAL;
6999 goto out;
7000}
7001
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007002int set_srv_agent_send(struct server *srv, const char *send)
7003{
7004 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7005 struct tcpcheck_var *var = NULL;
7006 char *str;
7007
7008 str = strdup(send);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007009 var = create_tcpcheck_var(ist("check.agent_string"));
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007010 if (str == NULL || var == NULL)
7011 goto error;
7012
7013 free_tcpcheck_vars(&rules->preset_vars);
7014
7015 var->data.type = SMP_T_STR;
7016 var->data.u.str.area = str;
7017 var->data.u.str.data = strlen(str);
7018 LIST_INIT(&var->list);
7019 LIST_ADDQ(&rules->preset_vars, &var->list);
7020
7021 return 1;
7022
7023 error:
7024 free(str);
7025 free(var);
7026 return 0;
7027}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007028
7029/* Parse the "agent-send" server keyword */
7030static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7031 char **errmsg)
7032{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007033 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007034 int err_code = 0;
7035
7036 if (!*(args[*cur_arg+1])) {
7037 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7038 goto error;
7039 }
7040
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007041 if (!rules) {
7042 rules = calloc(1, sizeof(*rules));
7043 if (!rules) {
7044 memprintf(errmsg, "out of memory.");
7045 goto error;
7046 }
7047 LIST_INIT(&rules->preset_vars);
7048 srv->agent.tcpcheck_rules = rules;
7049 }
7050
7051 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007052 memprintf(errmsg, "out of memory.");
7053 goto error;
7054 }
7055
7056 out:
7057 return err_code;
7058
7059 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007060 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007061 err_code |= ERR_ALERT | ERR_FATAL;
7062 goto out;
7063}
7064
7065/* Parse the "no-agent-send" server keyword */
7066static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7067 char **errmsg)
7068{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007069 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007070 return 0;
7071}
7072
Christopher Fauletce8111e2020-04-06 15:04:11 +02007073/* Parse the "check" server keyword */
7074static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7075 char **errmsg)
7076{
7077 srv->do_check = 1;
7078 return 0;
7079}
7080
7081/* Parse the "check-send-proxy" server keyword */
7082static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7083 char **errmsg)
7084{
7085 srv->check.send_proxy = 1;
7086 return 0;
7087}
7088
7089/* Parse the "check-via-socks4" server keyword */
7090static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7091 char **errmsg)
7092{
7093 srv->check.via_socks4 = 1;
7094 return 0;
7095}
7096
7097/* Parse the "no-check" server keyword */
7098static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7099 char **errmsg)
7100{
7101 deinit_srv_check(srv);
7102 return 0;
7103}
7104
7105/* Parse the "no-check-send-proxy" server keyword */
7106static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7107 char **errmsg)
7108{
7109 srv->check.send_proxy = 0;
7110 return 0;
7111}
7112
7113/* Parse the "rise" server keyword */
7114static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7115 char **errmsg)
7116{
7117 int err_code = 0;
7118
7119 if (!*args[*cur_arg + 1]) {
7120 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7121 goto error;
7122 }
7123
7124 srv->check.rise = atol(args[*cur_arg+1]);
7125 if (srv->check.rise <= 0) {
7126 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7127 goto error;
7128 }
7129
7130 if (srv->check.health)
7131 srv->check.health = srv->check.rise;
7132
7133 out:
7134 return err_code;
7135
7136 error:
7137 deinit_srv_agent_check(srv);
7138 err_code |= ERR_ALERT | ERR_FATAL;
7139 goto out;
7140 return 0;
7141}
7142
7143/* Parse the "fall" server keyword */
7144static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7145 char **errmsg)
7146{
7147 int err_code = 0;
7148
7149 if (!*args[*cur_arg + 1]) {
7150 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7151 goto error;
7152 }
7153
7154 srv->check.fall = atol(args[*cur_arg+1]);
7155 if (srv->check.fall <= 0) {
7156 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7157 goto error;
7158 }
7159
7160 out:
7161 return err_code;
7162
7163 error:
7164 deinit_srv_agent_check(srv);
7165 err_code |= ERR_ALERT | ERR_FATAL;
7166 goto out;
7167 return 0;
7168}
7169
7170/* Parse the "inter" server keyword */
7171static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7172 char **errmsg)
7173{
7174 const char *err = NULL;
7175 unsigned int delay;
7176 int err_code = 0;
7177
7178 if (!*(args[*cur_arg+1])) {
7179 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7180 goto error;
7181 }
7182
7183 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7184 if (err == PARSE_TIME_OVER) {
7185 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7186 args[*cur_arg+1], args[*cur_arg], srv->id);
7187 goto error;
7188 }
7189 else if (err == PARSE_TIME_UNDER) {
7190 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7191 args[*cur_arg+1], args[*cur_arg], srv->id);
7192 goto error;
7193 }
7194 else if (err) {
7195 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7196 *err, srv->id);
7197 goto error;
7198 }
7199 if (delay <= 0) {
7200 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7201 delay, args[*cur_arg], srv->id);
7202 goto error;
7203 }
7204 srv->check.inter = delay;
7205
7206 out:
7207 return err_code;
7208
7209 error:
7210 err_code |= ERR_ALERT | ERR_FATAL;
7211 goto out;
7212}
7213
7214
7215/* Parse the "fastinter" server keyword */
7216static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7217 char **errmsg)
7218{
7219 const char *err = NULL;
7220 unsigned int delay;
7221 int err_code = 0;
7222
7223 if (!*(args[*cur_arg+1])) {
7224 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7225 goto error;
7226 }
7227
7228 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7229 if (err == PARSE_TIME_OVER) {
7230 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7231 args[*cur_arg+1], args[*cur_arg], srv->id);
7232 goto error;
7233 }
7234 else if (err == PARSE_TIME_UNDER) {
7235 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7236 args[*cur_arg+1], args[*cur_arg], srv->id);
7237 goto error;
7238 }
7239 else if (err) {
7240 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7241 *err, srv->id);
7242 goto error;
7243 }
7244 if (delay <= 0) {
7245 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7246 delay, args[*cur_arg], srv->id);
7247 goto error;
7248 }
7249 srv->check.fastinter = delay;
7250
7251 out:
7252 return err_code;
7253
7254 error:
7255 err_code |= ERR_ALERT | ERR_FATAL;
7256 goto out;
7257}
7258
7259
7260/* Parse the "downinter" server keyword */
7261static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7262 char **errmsg)
7263{
7264 const char *err = NULL;
7265 unsigned int delay;
7266 int err_code = 0;
7267
7268 if (!*(args[*cur_arg+1])) {
7269 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7270 goto error;
7271 }
7272
7273 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7274 if (err == PARSE_TIME_OVER) {
7275 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7276 args[*cur_arg+1], args[*cur_arg], srv->id);
7277 goto error;
7278 }
7279 else if (err == PARSE_TIME_UNDER) {
7280 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7281 args[*cur_arg+1], args[*cur_arg], srv->id);
7282 goto error;
7283 }
7284 else if (err) {
7285 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7286 *err, srv->id);
7287 goto error;
7288 }
7289 if (delay <= 0) {
7290 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7291 delay, args[*cur_arg], srv->id);
7292 goto error;
7293 }
7294 srv->check.downinter = delay;
7295
7296 out:
7297 return err_code;
7298
7299 error:
7300 err_code |= ERR_ALERT | ERR_FATAL;
7301 goto out;
7302}
7303
7304/* Parse the "port" server keyword */
7305static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7306 char **errmsg)
7307{
7308 int err_code = 0;
7309
7310 if (!*(args[*cur_arg+1])) {
7311 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7312 goto error;
7313 }
7314
7315 global.maxsock++;
7316 srv->check.port = atol(args[*cur_arg+1]);
7317 srv->flags |= SRV_F_CHECKPORT;
7318
7319 out:
7320 return err_code;
7321
7322 error:
7323 err_code |= ERR_ALERT | ERR_FATAL;
7324 goto out;
7325}
7326
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007327static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007328 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7329 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7330 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007331 { 0, NULL, NULL },
7332}};
7333
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007334static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007335 { "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 +02007336 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7337 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7338 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7339 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7340 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007341 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
7342 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7343 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007344 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007345 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7346 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7347 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7348 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7349 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7350 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7351 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7352 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007353 { NULL, NULL, 0 },
7354}};
7355
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007356INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007357INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007358
Willy Tarreaubd741542010-03-16 18:46:54 +01007359/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007360 * Local variables:
7361 * c-indent-level: 8
7362 * c-basic-offset: 8
7363 * End:
7364 */