blob: 414c39e42181678d247dfd33773114f7ea8fef44 [file] [log] [blame]
Willy Tarreaubaaee002006-06-26 02:48:02 +02001/*
2 * Health-checks functions.
3 *
Willy Tarreau26c25062009-03-08 09:38:41 +01004 * Copyright 2000-2009 Willy Tarreau <w@1wt.eu>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +02005 * Copyright 2007-2009 Krzysztof Piotr Oledzki <ole@ans.pl>
Willy Tarreaubaaee002006-06-26 02:48:02 +02006 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version
10 * 2 of the License, or (at your option) any later version.
11 *
12 */
13
Willy Tarreaub8816082008-01-18 12:18:15 +010014#include <assert.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020015#include <ctype.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020016#include <errno.h>
17#include <fcntl.h>
Willy Tarreau9b39dc52014-07-08 00:54:10 +020018#include <signal.h>
Simon Horman0ba0e4a2015-01-30 11:23:00 +090019#include <stdarg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020020#include <stdio.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020021#include <stdlib.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020022#include <string.h>
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +020023#include <time.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020024#include <unistd.h>
Willy Tarreau9f6dc722019-03-01 11:15:10 +010025#include <sys/resource.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020026#include <sys/socket.h>
Dmitry Sivachenkocaf58982009-08-24 15:11:06 +040027#include <sys/types.h>
Simon Horman98637e52014-06-20 12:30:16 +090028#include <sys/wait.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020029#include <netinet/in.h>
Willy Tarreau1274bc42009-07-15 07:16:31 +020030#include <netinet/tcp.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020031#include <arpa/inet.h>
32
Christopher Fauletfd6c2292020-03-25 18:20:15 +010033#include <common/cfgparse.h>
Willy Tarreauc7e42382012-08-24 19:22:53 +020034#include <common/chunk.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020035#include <common/compat.h>
36#include <common/config.h>
37#include <common/mini-clist.h>
Willy Tarreau83749182007-04-15 20:56:27 +020038#include <common/standard.h>
Willy Tarreau2dd0d472006-06-29 17:53:05 +020039#include <common/time.h>
Christopher Fauletcfda8472017-10-20 15:40:23 +020040#include <common/hathreads.h>
Christopher Faulete5870d82020-04-15 11:32:03 +020041#include <common/http.h>
42#include <common/h1.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020043#include <common/htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020044
45#include <types/global.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020046#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010047#include <types/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020048
Gaetan Rivet707b52f2020-02-21 18:14:59 +010049#include <proto/action.h>
Christopher Fauletba3c68f2020-04-01 16:27:05 +020050#include <proto/arg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020051#include <proto/backend.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020052#include <proto/checks.h>
William Lallemand9ed62032016-11-21 17:49:11 +010053#include <proto/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020054#include <proto/fd.h>
Christopher Faulet14cd3162020-04-16 14:50:06 +020055#include <proto/http_htx.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020056#include <proto/log.h>
Willy Tarreau53a47662017-08-28 10:53:00 +020057#include <proto/mux_pt.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020058#include <proto/queue.h>
Willy Tarreauc6f4ce82009-06-10 11:09:37 +020059#include <proto/port_range.h>
Willy Tarreaue8c66af2008-01-13 18:40:14 +010060#include <proto/proto_tcp.h>
Baptiste Assmann69e273f2013-12-11 00:52:19 +010061#include <proto/protocol.h>
Willy Tarreau2b5652f2006-12-31 17:46:05 +010062#include <proto/proxy.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020063#include <proto/server.h>
Willy Tarreau48d6bf22016-06-21 16:27:34 +020064#include <proto/signal.h>
Willy Tarreau9e000c62011-03-10 14:03:36 +010065#include <proto/stream_interface.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020066#include <proto/task.h>
Gaetan Rivet13a50432020-02-21 18:13:44 +010067#include <proto/vars.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020068#include <proto/log.h>
69#include <proto/dns.h>
70#include <proto/proto_udp.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020071#include <proto/ssl_sock.h>
Christopher Fauletb7d30092020-03-30 15:19:03 +020072#include <proto/sample.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020073
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +020074static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *);
Christopher Faulet31c30fd2020-03-26 21:10:03 +010075
Christopher Faulet61cc8522020-04-20 14:54:42 +020076static int wake_srv_chk(struct conn_stream *cs);
77struct data_cb check_conn_cb = {
78 .wake = wake_srv_chk,
79 .name = "CHCK",
80};
Christopher Fauletd7e63962020-04-17 20:15:59 +020081
Christopher Fauletd7cee712020-04-21 13:45:00 +020082/* Global tree to share all tcp-checks */
83struct eb_root shared_tcpchecks = EB_ROOT;
Christopher Faulet5d503fc2020-03-30 20:34:34 +020084
85
Willy Tarreau8ceae722018-11-26 11:58:30 +010086DECLARE_STATIC_POOL(pool_head_email_alert, "email_alert", sizeof(struct email_alert));
87DECLARE_STATIC_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
Christopher Faulet31dff9b2017-10-23 15:45:20 +020088
Gaetan Rivet05d692d2020-02-14 17:42:54 +010089/* Dummy frontend used to create all checks sessions. */
90static struct proxy checks_fe;
Christopher Faulet31dff9b2017-10-23 15:45:20 +020091
Christopher Faulet61cc8522020-04-20 14:54:42 +020092/**************************************************************************/
93/************************ Handle check results ****************************/
94/**************************************************************************/
95struct check_status {
96 short result; /* one of SRV_CHK_* */
97 char *info; /* human readable short info */
98 char *desc; /* long description */
99};
100
101struct analyze_status {
102 char *desc; /* description */
103 unsigned char lr[HANA_OBS_SIZE]; /* result for l4/l7: 0 = ignore, 1 - error, 2 - OK */
104};
105
Simon Horman63a4a822012-03-19 07:24:41 +0900106static const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100107 [HCHK_STATUS_UNKNOWN] = { CHK_RES_UNKNOWN, "UNK", "Unknown" },
108 [HCHK_STATUS_INI] = { CHK_RES_UNKNOWN, "INI", "Initializing" },
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200109 [HCHK_STATUS_START] = { /* SPECIAL STATUS*/ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200110
Willy Tarreau23964182014-05-20 20:56:30 +0200111 /* Below we have finished checks */
112 [HCHK_STATUS_CHECKED] = { CHK_RES_NEUTRAL, "CHECKED", "No status change" },
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100113 [HCHK_STATUS_HANA] = { CHK_RES_FAILED, "HANA", "Health analyze" },
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100114
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100115 [HCHK_STATUS_SOCKERR] = { CHK_RES_FAILED, "SOCKERR", "Socket error" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200116
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100117 [HCHK_STATUS_L4OK] = { CHK_RES_PASSED, "L4OK", "Layer4 check passed" },
118 [HCHK_STATUS_L4TOUT] = { CHK_RES_FAILED, "L4TOUT", "Layer4 timeout" },
119 [HCHK_STATUS_L4CON] = { CHK_RES_FAILED, "L4CON", "Layer4 connection problem" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200120
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100121 [HCHK_STATUS_L6OK] = { CHK_RES_PASSED, "L6OK", "Layer6 check passed" },
122 [HCHK_STATUS_L6TOUT] = { CHK_RES_FAILED, "L6TOUT", "Layer6 timeout" },
123 [HCHK_STATUS_L6RSP] = { CHK_RES_FAILED, "L6RSP", "Layer6 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200124
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100125 [HCHK_STATUS_L7TOUT] = { CHK_RES_FAILED, "L7TOUT", "Layer7 timeout" },
126 [HCHK_STATUS_L7RSP] = { CHK_RES_FAILED, "L7RSP", "Layer7 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200127
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200128 [HCHK_STATUS_L57DATA] = { /* DUMMY STATUS */ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200129
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100130 [HCHK_STATUS_L7OKD] = { CHK_RES_PASSED, "L7OK", "Layer7 check passed" },
131 [HCHK_STATUS_L7OKCD] = { CHK_RES_CONDPASS, "L7OKC", "Layer7 check conditionally passed" },
132 [HCHK_STATUS_L7STS] = { CHK_RES_FAILED, "L7STS", "Layer7 wrong status" },
Simon Horman98637e52014-06-20 12:30:16 +0900133
134 [HCHK_STATUS_PROCERR] = { CHK_RES_FAILED, "PROCERR", "External check error" },
135 [HCHK_STATUS_PROCTOUT] = { CHK_RES_FAILED, "PROCTOUT", "External check timeout" },
Cyril Bonté77010d82014-08-07 01:55:37 +0200136 [HCHK_STATUS_PROCOK] = { CHK_RES_PASSED, "PROCOK", "External check passed" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200137};
138
Simon Horman63a4a822012-03-19 07:24:41 +0900139static const struct analyze_status analyze_statuses[HANA_STATUS_SIZE] = { /* 0: ignore, 1: error, 2: OK */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100140 [HANA_STATUS_UNKNOWN] = { "Unknown", { 0, 0 }},
141
142 [HANA_STATUS_L4_OK] = { "L4 successful connection", { 2, 0 }},
143 [HANA_STATUS_L4_ERR] = { "L4 unsuccessful connection", { 1, 1 }},
144
145 [HANA_STATUS_HTTP_OK] = { "Correct http response", { 0, 2 }},
146 [HANA_STATUS_HTTP_STS] = { "Wrong http response", { 0, 1 }},
147 [HANA_STATUS_HTTP_HDRRSP] = { "Invalid http response (headers)", { 0, 1 }},
148 [HANA_STATUS_HTTP_RSP] = { "Invalid http response", { 0, 1 }},
149
150 [HANA_STATUS_HTTP_READ_ERROR] = { "Read error (http)", { 0, 1 }},
151 [HANA_STATUS_HTTP_READ_TIMEOUT] = { "Read timeout (http)", { 0, 1 }},
152 [HANA_STATUS_HTTP_BROKEN_PIPE] = { "Close from server (http)", { 0, 1 }},
153};
154
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100155/* checks if <err> is a real error for errno or one that can be ignored, and
156 * return 0 for these ones or <err> for real ones.
157 */
158static inline int unclean_errno(int err)
159{
160 if (err == EAGAIN || err == EINPROGRESS ||
161 err == EISCONN || err == EALREADY)
162 return 0;
163 return err;
164}
165
Christopher Faulet61cc8522020-04-20 14:54:42 +0200166/* Converts check_status code to description */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200167const char *get_check_status_description(short check_status) {
168
169 const char *desc;
170
171 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200172 desc = check_statuses[check_status].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200173 else
174 desc = NULL;
175
176 if (desc && *desc)
177 return desc;
178 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200179 return check_statuses[HCHK_STATUS_UNKNOWN].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200180}
181
Christopher Faulet61cc8522020-04-20 14:54:42 +0200182/* Converts check_status code to short info */
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200183const char *get_check_status_info(short check_status) {
184
185 const char *info;
186
187 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200188 info = check_statuses[check_status].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200189 else
190 info = NULL;
191
192 if (info && *info)
193 return info;
194 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200195 return check_statuses[HCHK_STATUS_UNKNOWN].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200196}
197
Christopher Faulet61cc8522020-04-20 14:54:42 +0200198/* Convert analyze_status to description */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100199const char *get_analyze_status(short analyze_status) {
200
201 const char *desc;
202
203 if (analyze_status < HANA_STATUS_SIZE)
204 desc = analyze_statuses[analyze_status].desc;
205 else
206 desc = NULL;
207
208 if (desc && *desc)
209 return desc;
210 else
211 return analyze_statuses[HANA_STATUS_UNKNOWN].desc;
212}
213
Christopher Faulet61cc8522020-04-20 14:54:42 +0200214/* Sets check->status, update check->duration and fill check->result with an
215 * adequate CHK_RES_* value. The new check->health is computed based on the
216 * result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200217 *
Christopher Faulet61cc8522020-04-20 14:54:42 +0200218 * Shows information in logs about failed health check if server is UP or
219 * succeeded health checks if server is DOWN.
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200220 */
Simon Horman4a741432013-02-23 15:35:38 +0900221static void set_server_check_status(struct check *check, short status, const char *desc)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100222{
Simon Horman4a741432013-02-23 15:35:38 +0900223 struct server *s = check->server;
Willy Tarreaubef1b322014-05-13 21:01:39 +0200224 short prev_status = check->status;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200225 int report = 0;
Simon Horman4a741432013-02-23 15:35:38 +0900226
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200227 if (status == HCHK_STATUS_START) {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100228 check->result = CHK_RES_UNKNOWN; /* no result yet */
Simon Horman4a741432013-02-23 15:35:38 +0900229 check->desc[0] = '\0';
230 check->start = now;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200231 return;
232 }
233
Simon Horman4a741432013-02-23 15:35:38 +0900234 if (!check->status)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200235 return;
236
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200237 if (desc && *desc) {
Simon Horman4a741432013-02-23 15:35:38 +0900238 strncpy(check->desc, desc, HCHK_DESC_LEN-1);
239 check->desc[HCHK_DESC_LEN-1] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200240 } else
Simon Horman4a741432013-02-23 15:35:38 +0900241 check->desc[0] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200242
Simon Horman4a741432013-02-23 15:35:38 +0900243 check->status = status;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200244 if (check_statuses[status].result)
Simon Horman4a741432013-02-23 15:35:38 +0900245 check->result = check_statuses[status].result;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200246
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100247 if (status == HCHK_STATUS_HANA)
Simon Horman4a741432013-02-23 15:35:38 +0900248 check->duration = -1;
249 else if (!tv_iszero(&check->start)) {
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200250 /* set_server_check_status() may be called more than once */
Simon Horman4a741432013-02-23 15:35:38 +0900251 check->duration = tv_ms_elapsed(&check->start, &now);
252 tv_zero(&check->start);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200253 }
254
Willy Tarreau23964182014-05-20 20:56:30 +0200255 /* no change is expected if no state change occurred */
256 if (check->result == CHK_RES_NEUTRAL)
257 return;
258
Olivier Houchard0923fa42019-01-11 18:43:04 +0100259 /* If the check was really just sending a mail, it won't have an
260 * associated server, so we're done now.
261 */
262 if (!s)
263 return;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200264 report = 0;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200265
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200266 switch (check->result) {
267 case CHK_RES_FAILED:
Willy Tarreau12634e12014-05-23 11:32:36 +0200268 /* Failure to connect to the agent as a secondary check should not
269 * cause the server to be marked down.
270 */
271 if ((!(check->state & CHK_ST_AGENT) ||
Simon Hormaneaabd522015-02-26 11:26:17 +0900272 (check->status >= HCHK_STATUS_L57DATA)) &&
Christopher Fauletb119a792018-05-02 12:12:45 +0200273 (check->health > 0)) {
Olivier Houchard7059c552019-03-08 18:49:32 +0100274 _HA_ATOMIC_ADD(&s->counters.failed_checks, 1);
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200275 report = 1;
276 check->health--;
277 if (check->health < check->rise)
278 check->health = 0;
279 }
280 break;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200281
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200282 case CHK_RES_PASSED:
283 case CHK_RES_CONDPASS: /* "condpass" cannot make the first step but it OK after a "passed" */
284 if ((check->health < check->rise + check->fall - 1) &&
285 (check->result == CHK_RES_PASSED || check->health > 0)) {
286 report = 1;
287 check->health++;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200288
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200289 if (check->health >= check->rise)
290 check->health = check->rise + check->fall - 1; /* OK now */
291 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200292
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200293 /* clear consecutive_errors if observing is enabled */
294 if (s->onerror)
295 s->consecutive_errors = 0;
296 break;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100297
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200298 default:
299 break;
300 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200301
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200302 if (s->proxy->options2 & PR_O2_LOGHCHKS &&
303 (status != prev_status || report)) {
304 chunk_printf(&trash,
Willy Tarreau12634e12014-05-23 11:32:36 +0200305 "%s check for %sserver %s/%s %s%s",
306 (check->state & CHK_ST_AGENT) ? "Agent" : "Health",
Willy Tarreauc93cd162014-05-13 15:54:22 +0200307 s->flags & SRV_F_BACKUP ? "backup " : "",
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100308 s->proxy->id, s->id,
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100309 (check->result == CHK_RES_CONDPASS) ? "conditionally ":"",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200310 (check->result >= CHK_RES_PASSED) ? "succeeded" : "failed");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200311
Emeric Brun5a133512017-10-19 14:42:30 +0200312 srv_append_status(&trash, s, check, -1, 0);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200313
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100314 chunk_appendf(&trash, ", status: %d/%d %s",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200315 (check->health >= check->rise) ? check->health - check->rise + 1 : check->health,
316 (check->health >= check->rise) ? check->fall : check->rise,
317 (check->health >= check->rise) ? (s->uweight ? "UP" : "DRAIN") : "DOWN");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200318
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200319 ha_warning("%s.\n", trash.area);
320 send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area);
321 send_email_alert(s, LOG_INFO, "%s", trash.area);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200322 }
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200323}
324
Willy Tarreau4eec5472014-05-20 22:32:27 +0200325/* Marks the check <check>'s server down if the current check is already failed
326 * and the server is not down yet nor in maintenance.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200327 */
Willy Tarreau4eec5472014-05-20 22:32:27 +0200328static void check_notify_failure(struct check *check)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200329{
Simon Horman4a741432013-02-23 15:35:38 +0900330 struct server *s = check->server;
Simon Hormane0d1bfb2011-06-21 14:34:58 +0900331
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200332 /* The agent secondary check should only cause a server to be marked
333 * as down if check->status is HCHK_STATUS_L7STS, which indicates
334 * that the agent returned "fail", "stopped" or "down".
335 * The implication here is that failure to connect to the agent
336 * as a secondary check should not cause the server to be marked
337 * down. */
338 if ((check->state & CHK_ST_AGENT) && check->status != HCHK_STATUS_L7STS)
339 return;
340
Willy Tarreau4eec5472014-05-20 22:32:27 +0200341 if (check->health > 0)
342 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100343
Willy Tarreau4eec5472014-05-20 22:32:27 +0200344 /* We only report a reason for the check if we did not do so previously */
Emeric Brun5a133512017-10-19 14:42:30 +0200345 srv_set_stopped(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200346}
347
Willy Tarreauaf549582014-05-16 17:37:50 +0200348/* Marks the check <check> as valid and tries to set its server up, provided
Willy Tarreau3e048382014-05-21 10:30:54 +0200349 * it isn't in maintenance, it is not tracking a down server and other checks
350 * comply. The rule is simple : by default, a server is up, unless any of the
351 * following conditions is true :
352 * - health check failed (check->health < rise)
353 * - agent check failed (agent->health < rise)
354 * - the server tracks a down server (track && track->state == STOPPED)
355 * Note that if the server has a slowstart, it will switch to STARTING instead
356 * of RUNNING. Also, only the health checks support the nolb mode, so the
357 * agent's success may not take the server out of this mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200358 */
Willy Tarreau3e048382014-05-21 10:30:54 +0200359static void check_notify_success(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200360{
Simon Horman4a741432013-02-23 15:35:38 +0900361 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100362
Emeric Brun52a91d32017-08-31 14:41:55 +0200363 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200364 return;
Cyril Bontécd19e512010-01-31 22:34:03 +0100365
Emeric Brun52a91d32017-08-31 14:41:55 +0200366 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreauaf549582014-05-16 17:37:50 +0200367 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100368
Willy Tarreau3e048382014-05-21 10:30:54 +0200369 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
370 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100371
Willy Tarreau3e048382014-05-21 10:30:54 +0200372 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
373 return;
Willy Tarreauaf549582014-05-16 17:37:50 +0200374
Emeric Brun52a91d32017-08-31 14:41:55 +0200375 if ((check->state & CHK_ST_AGENT) && s->next_state == SRV_ST_STOPPING)
Willy Tarreau3e048382014-05-21 10:30:54 +0200376 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100377
Emeric Brun5a133512017-10-19 14:42:30 +0200378 srv_set_running(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100379}
380
Willy Tarreaudb58b792014-05-21 13:57:23 +0200381/* Marks the check <check> as valid and tries to set its server into stopping mode
382 * if it was running or starting, and provided it isn't in maintenance and other
383 * checks comply. The conditions for the server to be marked in stopping mode are
384 * the same as for it to be turned up. Also, only the health checks support the
385 * nolb mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200386 */
Willy Tarreaudb58b792014-05-21 13:57:23 +0200387static void check_notify_stopping(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200388{
Simon Horman4a741432013-02-23 15:35:38 +0900389 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100390
Emeric Brun52a91d32017-08-31 14:41:55 +0200391 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200392 return;
393
Willy Tarreaudb58b792014-05-21 13:57:23 +0200394 if (check->state & CHK_ST_AGENT)
395 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100396
Emeric Brun52a91d32017-08-31 14:41:55 +0200397 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreaudb58b792014-05-21 13:57:23 +0200398 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100399
Willy Tarreaudb58b792014-05-21 13:57:23 +0200400 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
401 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100402
Willy Tarreaudb58b792014-05-21 13:57:23 +0200403 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
404 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100405
Willy Tarreaub26881a2017-12-23 11:16:49 +0100406 srv_set_stopping(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100407}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200408
Willy Tarreau9fe7aae2013-12-31 23:47:37 +0100409/* note: use health_adjust() only, which first checks that the observe mode is
410 * enabled.
411 */
412void __health_adjust(struct server *s, short status)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100413{
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100414 int failed;
415 int expire;
416
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100417 if (s->observe >= HANA_OBS_SIZE)
418 return;
419
Willy Tarreaubb956662013-01-24 00:37:39 +0100420 if (status >= HANA_STATUS_SIZE || !analyze_statuses[status].desc)
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100421 return;
422
423 switch (analyze_statuses[status].lr[s->observe - 1]) {
424 case 1:
425 failed = 1;
426 break;
427
428 case 2:
429 failed = 0;
430 break;
431
432 default:
433 return;
434 }
435
436 if (!failed) {
437 /* good: clear consecutive_errors */
438 s->consecutive_errors = 0;
439 return;
440 }
441
Olivier Houchard7059c552019-03-08 18:49:32 +0100442 _HA_ATOMIC_ADD(&s->consecutive_errors, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100443
444 if (s->consecutive_errors < s->consecutive_errors_limit)
445 return;
446
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100447 chunk_printf(&trash, "Detected %d consecutive errors, last one was: %s",
448 s->consecutive_errors, get_analyze_status(status));
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100449
450 switch (s->onerror) {
451 case HANA_ONERR_FASTINTER:
452 /* force fastinter - nothing to do here as all modes force it */
453 break;
454
455 case HANA_ONERR_SUDDTH:
456 /* simulate a pre-fatal failed health check */
Simon Horman58c32972013-11-25 10:46:38 +0900457 if (s->check.health > s->check.rise)
458 s->check.health = s->check.rise + 1;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100459
460 /* no break - fall through */
461
462 case HANA_ONERR_FAILCHK:
463 /* simulate a failed health check */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200464 set_server_check_status(&s->check, HCHK_STATUS_HANA,
465 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200466 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100467 break;
468
469 case HANA_ONERR_MARKDWN:
470 /* mark server down */
Simon Horman58c32972013-11-25 10:46:38 +0900471 s->check.health = s->check.rise;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200472 set_server_check_status(&s->check, HCHK_STATUS_HANA,
473 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200474 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100475 break;
476
477 default:
478 /* write a warning? */
479 break;
480 }
481
482 s->consecutive_errors = 0;
Olivier Houchard7059c552019-03-08 18:49:32 +0100483 _HA_ATOMIC_ADD(&s->counters.failed_hana, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100484
Simon Horman66183002013-02-23 10:16:43 +0900485 if (s->check.fastinter) {
486 expire = tick_add(now_ms, MS_TO_TICKS(s->check.fastinter));
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300487 if (s->check.task->expire > expire) {
Willy Tarreau5b3a2022012-09-28 15:01:02 +0200488 s->check.task->expire = expire;
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300489 /* requeue check task with new expire */
490 task_queue(s->check.task);
491 }
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100492 }
Willy Tarreauef781042010-01-27 11:53:01 +0100493}
494
Christopher Faulet61cc8522020-04-20 14:54:42 +0200495/* Checks the connection. If an error has already been reported or the socket is
Willy Tarreau20a18342013-12-05 00:31:46 +0100496 * closed, keep errno intact as it is supposed to contain the valid error code.
497 * If no error is reported, check the socket's error queue using getsockopt().
498 * Warning, this must be done only once when returning from poll, and never
499 * after an I/O error was attempted, otherwise the error queue might contain
500 * inconsistent errors. If an error is detected, the CO_FL_ERROR is set on the
501 * socket. Returns non-zero if an error was reported, zero if everything is
502 * clean (including a properly closed socket).
503 */
504static int retrieve_errno_from_socket(struct connection *conn)
505{
506 int skerr;
507 socklen_t lskerr = sizeof(skerr);
508
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100509 if (conn->flags & CO_FL_ERROR && (unclean_errno(errno) || !conn->ctrl))
Willy Tarreau20a18342013-12-05 00:31:46 +0100510 return 1;
511
Willy Tarreau3c728722014-01-23 13:50:42 +0100512 if (!conn_ctrl_ready(conn))
Willy Tarreau20a18342013-12-05 00:31:46 +0100513 return 0;
514
Willy Tarreau585744b2017-08-24 14:31:19 +0200515 if (getsockopt(conn->handle.fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == 0)
Willy Tarreau20a18342013-12-05 00:31:46 +0100516 errno = skerr;
517
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100518 errno = unclean_errno(errno);
Willy Tarreau20a18342013-12-05 00:31:46 +0100519
520 if (!errno) {
521 /* we could not retrieve an error, that does not mean there is
522 * none. Just don't change anything and only report the prior
523 * error if any.
524 */
525 if (conn->flags & CO_FL_ERROR)
526 return 1;
527 else
528 return 0;
529 }
530
531 conn->flags |= CO_FL_ERROR | CO_FL_SOCK_WR_SH | CO_FL_SOCK_RD_SH;
532 return 1;
533}
534
Christopher Faulet61cc8522020-04-20 14:54:42 +0200535/* Tries to collect as much information as possible on the connection status,
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100536 * and adjust the server status accordingly. It may make use of <errno_bck>
537 * if non-null when the caller is absolutely certain of its validity (eg:
538 * checked just after a syscall). If the caller doesn't have a valid errno,
539 * it can pass zero, and retrieve_errno_from_socket() will be called to try
540 * to extract errno from the socket. If no error is reported, it will consider
541 * the <expired> flag. This is intended to be used when a connection error was
542 * reported in conn->flags or when a timeout was reported in <expired>. The
543 * function takes care of not updating a server status which was already set.
544 * All situations where at least one of <expired> or CO_FL_ERROR are set
545 * produce a status.
546 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200547static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100548{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200549 struct conn_stream *cs = check->cs;
550 struct connection *conn = cs_conn(cs);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100551 const char *err_msg;
Willy Tarreau83061a82018-07-13 11:56:34 +0200552 struct buffer *chk;
Willy Tarreau213c6782014-10-02 14:51:02 +0200553 int step;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100554
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100555 if (check->result != CHK_RES_UNKNOWN)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100556 return;
557
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100558 errno = unclean_errno(errno_bck);
559 if (conn && errno)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100560 retrieve_errno_from_socket(conn);
561
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200562 if (conn && !(conn->flags & CO_FL_ERROR) &&
563 !(cs->flags & CS_FL_ERROR) && !expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100564 return;
565
566 /* we'll try to build a meaningful error message depending on the
567 * context of the error possibly present in conn->err_code, and the
568 * socket error possibly collected above. This is useful to know the
569 * exact step of the L6 layer (eg: SSL handshake).
570 */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200571 chk = get_trash_chunk();
572
Christopher Faulet799f3a42020-04-07 12:06:14 +0200573 if (check->type == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +0200574 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200575 step = tcpcheck_get_step_id(check, NULL);
Willy Tarreau213c6782014-10-02 14:51:02 +0200576 if (!step)
577 chunk_printf(chk, " at initial connection step of tcp-check");
578 else {
579 chunk_printf(chk, " at step %d of tcp-check", step);
580 /* we were looking for a string */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200581 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
582 if (check->current_step->connect.port)
583 chunk_appendf(chk, " (connect port %d)" ,check->current_step->connect.port);
Willy Tarreau213c6782014-10-02 14:51:02 +0200584 else
585 chunk_appendf(chk, " (connect)");
586 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200587 else if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
588 struct tcpcheck_expect *expect = &check->current_step->expect;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100589
590 switch (expect->type) {
591 case TCPCHK_EXPECT_STRING:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200592 chunk_appendf(chk, " (expect string '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100593 break;
594 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200595 chunk_appendf(chk, " (expect binary '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Gaetan Rivetb616add2020-02-07 15:37:17 +0100596 break;
597 case TCPCHK_EXPECT_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200598 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100599 break;
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100600 case TCPCHK_EXPECT_REGEX_BINARY:
601 chunk_appendf(chk, " (expect binary regex)");
602 break;
Christopher Faulete5870d82020-04-15 11:32:03 +0200603 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200604 chunk_appendf(chk, " (expect HTTP status codes)");
Christopher Faulete5870d82020-04-15 11:32:03 +0200605 break;
606 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
607 chunk_appendf(chk, " (expect HTTP status regex)");
608 break;
609 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200610 chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), istptr(expect->data));
Christopher Faulete5870d82020-04-15 11:32:03 +0200611 break;
612 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
613 chunk_appendf(chk, " (expect HTTP body regex)");
614 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200615 case TCPCHK_EXPECT_CUSTOM:
616 chunk_appendf(chk, " (expect custom function)");
617 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100618 case TCPCHK_EXPECT_UNDEF:
619 chunk_appendf(chk, " (undefined expect!)");
620 break;
621 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200622 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200623 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200624 chunk_appendf(chk, " (send)");
625 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200626
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200627 if (check->current_step && check->current_step->comment)
628 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200629 }
630 }
631
Willy Tarreau00149122017-10-04 18:05:01 +0200632 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100633 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200634 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
635 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100636 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200637 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
638 chk->area);
639 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100640 }
641 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100642 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200643 chunk_printf(&trash, "%s%s", strerror(errno),
644 chk->area);
645 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100646 }
647 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200648 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100649 }
650 }
651
Willy Tarreau00149122017-10-04 18:05:01 +0200652 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200653 /* NOTE: this is reported after <fall> tries */
654 chunk_printf(chk, "No port available for the TCP connection");
655 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
656 }
657
Willy Tarreau00149122017-10-04 18:05:01 +0200658 if (!conn) {
659 /* connection allocation error before the connection was established */
660 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
661 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100662 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100663 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200664 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100665 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
666 else if (expired)
667 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200668
669 /*
670 * might be due to a server IP change.
671 * Let's trigger a DNS resolution if none are currently running.
672 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100673 if (check->server)
674 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200675
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100676 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100677 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100678 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200679 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100680 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
681 else if (expired)
682 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
683 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200684 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100685 /* I/O error after connection was established and before we could diagnose */
686 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
687 }
688 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200689 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
690
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100691 /* connection established but expired check */
Christopher Faulet811f78c2020-04-01 11:10:27 +0200692 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
693 tout = check->current_step->expect.tout_status;
694 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100695 }
696
697 return;
698}
699
Willy Tarreaubaaee002006-06-26 02:48:02 +0200700
Christopher Faulet61cc8522020-04-20 14:54:42 +0200701/**************************************************************************/
702/*************** Init/deinit tcp-check rules and ruleset ******************/
703/**************************************************************************/
704/* Releases memory allocated for a log-format string */
705static void free_tcpcheck_fmt(struct list *fmt)
Willy Tarreau20bea422012-07-06 12:00:49 +0200706{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200707 struct logformat_node *lf, *lfb;
Willy Tarreau2d351b62013-12-05 02:36:25 +0100708
Christopher Faulet61cc8522020-04-20 14:54:42 +0200709 list_for_each_entry_safe(lf, lfb, fmt, list) {
710 LIST_DEL(&lf->list);
711 release_sample_expr(lf->expr);
712 free(lf->arg);
713 free(lf);
Willy Tarreau2d351b62013-12-05 02:36:25 +0100714 }
Willy Tarreau20bea422012-07-06 12:00:49 +0200715}
716
Christopher Faulet61cc8522020-04-20 14:54:42 +0200717/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
718static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
Willy Tarreau2e993902011-10-31 11:53:20 +0100719{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200720 if (!hdr)
721 return;
Willy Tarreau4fc49a92019-05-05 06:54:22 +0200722
Christopher Faulet61cc8522020-04-20 14:54:42 +0200723 free_tcpcheck_fmt(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200724 istfree(&hdr->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200725 free(hdr);
Willy Tarreau2e993902011-10-31 11:53:20 +0100726}
727
Christopher Faulet61cc8522020-04-20 14:54:42 +0200728/* Releases memory allocated for an HTTP header list used in a tcp-check send
729 * rule
Willy Tarreau894c6422017-10-04 15:58:52 +0200730 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200731static void free_tcpcheck_http_hdrs(struct list *hdrs)
Willy Tarreau894c6422017-10-04 15:58:52 +0200732{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200733 struct tcpcheck_http_hdr *hdr, *bhdr;
Willy Tarreau894c6422017-10-04 15:58:52 +0200734
Christopher Faulet61cc8522020-04-20 14:54:42 +0200735 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
736 LIST_DEL(&hdr->list);
737 free_tcpcheck_http_hdr(hdr);
Willy Tarreau894c6422017-10-04 15:58:52 +0200738 }
Willy Tarreau894c6422017-10-04 15:58:52 +0200739}
740
Christopher Faulet61cc8522020-04-20 14:54:42 +0200741/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
742 * tcp-check was allocated using a memory pool (it is used to instantiate email
743 * alerts).
Christopher Faulet95226db2020-04-15 11:34:04 +0200744 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200745static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
Christopher Faulet95226db2020-04-15 11:34:04 +0200746{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200747 if (!rule)
748 return;
Christopher Faulet95226db2020-04-15 11:34:04 +0200749
Christopher Faulet61cc8522020-04-20 14:54:42 +0200750 free(rule->comment);
751 switch (rule->action) {
752 case TCPCHK_ACT_SEND:
753 switch (rule->send.type) {
754 case TCPCHK_SEND_STRING:
755 case TCPCHK_SEND_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200756 istfree(&rule->send.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200757 break;
758 case TCPCHK_SEND_STRING_LF:
759 case TCPCHK_SEND_BINARY_LF:
760 free_tcpcheck_fmt(&rule->send.fmt);
761 break;
762 case TCPCHK_SEND_HTTP:
763 free(rule->send.http.meth.str.area);
764 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200765 istfree(&rule->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200766 else
767 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
Christopher Fauletb61caf42020-04-21 10:57:42 +0200768 istfree(&rule->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200769 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
770 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +0200771 istfree(&rule->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200772 else
773 free_tcpcheck_fmt(&rule->send.http.body_fmt);
774 break;
775 case TCPCHK_SEND_UNDEF:
776 break;
777 }
778 break;
779 case TCPCHK_ACT_EXPECT:
780 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
781 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
782 release_sample_expr(rule->expect.status_expr);
783 switch (rule->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +0200784 case TCPCHK_EXPECT_HTTP_STATUS:
785 free(rule->expect.codes.codes);
786 break;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200787 case TCPCHK_EXPECT_STRING:
788 case TCPCHK_EXPECT_BINARY:
Christopher Faulet61cc8522020-04-20 14:54:42 +0200789 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +0200790 istfree(&rule->expect.data);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200791 break;
792 case TCPCHK_EXPECT_REGEX:
793 case TCPCHK_EXPECT_REGEX_BINARY:
794 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
795 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
796 regex_free(rule->expect.regex);
797 break;
798 case TCPCHK_EXPECT_CUSTOM:
799 case TCPCHK_EXPECT_UNDEF:
800 break;
801 }
802 break;
803 case TCPCHK_ACT_CONNECT:
804 free(rule->connect.sni);
805 free(rule->connect.alpn);
806 release_sample_expr(rule->connect.port_expr);
807 break;
808 case TCPCHK_ACT_COMMENT:
809 break;
810 case TCPCHK_ACT_ACTION_KW:
811 free(rule->action_kw.rule);
812 break;
Christopher Faulet95226db2020-04-15 11:34:04 +0200813 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200814
815 if (in_pool)
816 pool_free(pool_head_tcpcheck_rule, rule);
817 else
818 free(rule);
Christopher Faulet95226db2020-04-15 11:34:04 +0200819}
820
Christopher Faulet61cc8522020-04-20 14:54:42 +0200821/* Creates a tcp-check variable used in preset variables before executing a
822 * tcp-check ruleset.
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100823 */
Christopher Fauletb61caf42020-04-21 10:57:42 +0200824static struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100825{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200826 struct tcpcheck_var *var = NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100827
Christopher Faulet61cc8522020-04-20 14:54:42 +0200828 var = calloc(1, sizeof(*var));
829 if (var == NULL)
830 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100831
Christopher Fauletb61caf42020-04-21 10:57:42 +0200832 var->name = istdup(name);
833 if (!isttest(var->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200834 free(var);
835 return NULL;
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +0100836 }
Simon Horman98637e52014-06-20 12:30:16 +0900837
Christopher Faulet61cc8522020-04-20 14:54:42 +0200838 LIST_INIT(&var->list);
839 return var;
Simon Horman98637e52014-06-20 12:30:16 +0900840}
841
Christopher Faulet61cc8522020-04-20 14:54:42 +0200842/* Releases memory allocated for a preset tcp-check variable */
843static void free_tcpcheck_var(struct tcpcheck_var *var)
Simon Horman98637e52014-06-20 12:30:16 +0900844{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200845 if (!var)
846 return;
847
Christopher Fauletb61caf42020-04-21 10:57:42 +0200848 istfree(&var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200849 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
850 free(var->data.u.str.area);
851 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
852 free(var->data.u.meth.str.area);
853 free(var);
Simon Horman98637e52014-06-20 12:30:16 +0900854}
855
Christopher Faulet61cc8522020-04-20 14:54:42 +0200856/* Releases a list of preset tcp-check variables */
857static void free_tcpcheck_vars(struct list *vars)
Simon Horman98637e52014-06-20 12:30:16 +0900858{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200859 struct tcpcheck_var *var, *back;
Christopher Fauletcfda8472017-10-20 15:40:23 +0200860
Christopher Faulet61cc8522020-04-20 14:54:42 +0200861 list_for_each_entry_safe(var, back, vars, list) {
862 LIST_DEL(&var->list);
863 free_tcpcheck_var(var);
864 }
Simon Horman98637e52014-06-20 12:30:16 +0900865}
866
Christopher Faulet61cc8522020-04-20 14:54:42 +0200867/* Duplicate a list of preset tcp-check variables */
868int dup_tcpcheck_vars(struct list *dst, struct list *src)
Simon Horman98637e52014-06-20 12:30:16 +0900869{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200870 struct tcpcheck_var *var, *new = NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900871
Christopher Faulet61cc8522020-04-20 14:54:42 +0200872 list_for_each_entry(var, src, list) {
Christopher Fauletb61caf42020-04-21 10:57:42 +0200873 new = create_tcpcheck_var(var->name);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200874 if (!new)
875 goto error;
876 new->data.type = var->data.type;
877 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
878 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
879 goto error;
880 if (var->data.type == SMP_T_STR)
881 new->data.u.str.area[new->data.u.str.data] = 0;
882 }
883 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
884 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
885 goto error;
886 new->data.u.str.area[new->data.u.str.data] = 0;
887 new->data.u.meth.meth = var->data.u.meth.meth;
888 }
889 else
890 new->data.u = var->data.u;
891 LIST_ADDQ(dst, &new->list);
892 }
893 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900894
Christopher Faulet61cc8522020-04-20 14:54:42 +0200895 error:
896 free(new);
897 return 0;
898}
Christopher Fauletcfda8472017-10-20 15:40:23 +0200899
Christopher Faulet61cc8522020-04-20 14:54:42 +0200900/* Looks for a shared tcp-check ruleset given its name. */
901static struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
902{
903 struct tcpcheck_ruleset *rs;
Christopher Fauletd7cee712020-04-21 13:45:00 +0200904 struct ebpt_node *node;
Simon Horman98637e52014-06-20 12:30:16 +0900905
Christopher Fauletd7cee712020-04-21 13:45:00 +0200906 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
907 if (node) {
908 rs = container_of(node, typeof(*rs), node);
909 return rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +0200910 }
911 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900912}
913
Christopher Faulet61cc8522020-04-20 14:54:42 +0200914/* Creates a new shared tcp-check ruleset */
915static struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
Simon Horman98637e52014-06-20 12:30:16 +0900916{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200917 struct tcpcheck_ruleset *rs;
Simon Horman98637e52014-06-20 12:30:16 +0900918
Christopher Faulet61cc8522020-04-20 14:54:42 +0200919 rs = calloc(1, sizeof(*rs));
920 if (rs == NULL)
921 return NULL;
922
Christopher Fauletd7cee712020-04-21 13:45:00 +0200923 rs->node.key = strdup(name);
924 if (rs->node.key == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +0200925 free(rs);
926 return NULL;
Simon Horman98637e52014-06-20 12:30:16 +0900927 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200928
Christopher Faulet61cc8522020-04-20 14:54:42 +0200929 LIST_INIT(&rs->rules);
Christopher Fauletd7cee712020-04-21 13:45:00 +0200930 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200931 return rs;
Simon Horman98637e52014-06-20 12:30:16 +0900932}
933
Christopher Faulet61cc8522020-04-20 14:54:42 +0200934/* Releases memory allocated by a tcp-check ruleset. */
935static void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
Simon Horman98637e52014-06-20 12:30:16 +0900936{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200937 struct tcpcheck_rule *r, *rb;
938 if (!rs)
939 return;
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200940
Christopher Fauletd7cee712020-04-21 13:45:00 +0200941 ebpt_delete(&rs->node);
942 free(rs->node.key);
Christopher Faulet61cc8522020-04-20 14:54:42 +0200943 list_for_each_entry_safe(r, rb, &rs->rules, list) {
944 LIST_DEL(&r->list);
945 free_tcpcheck(r, 0);
946 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200947 free(rs);
Simon Horman98637e52014-06-20 12:30:16 +0900948}
949
Christopher Faulet61cc8522020-04-20 14:54:42 +0200950
951/**************************************************************************/
952/**************** Everything about tcp-checks execution *******************/
953/**************************************************************************/
954/* Returns the id of a step in a tcp-check ruleset */
955static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau48d6bf22016-06-21 16:27:34 +0200956{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200957 if (!rule)
958 rule = check->current_step;
Simon Horman98637e52014-06-20 12:30:16 +0900959
Christopher Faulet61cc8522020-04-20 14:54:42 +0200960 /* no last started step => first step */
961 if (!rule)
Simon Horman98637e52014-06-20 12:30:16 +0900962 return 1;
Simon Horman98637e52014-06-20 12:30:16 +0900963
Christopher Faulet61cc8522020-04-20 14:54:42 +0200964 /* last step is the first implicit connect */
965 if (rule->index == 0 &&
966 rule->action == TCPCHK_ACT_CONNECT &&
967 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
968 return 0;
Simon Horman98637e52014-06-20 12:30:16 +0900969
Christopher Faulet61cc8522020-04-20 14:54:42 +0200970 return rule->index + 1;
Simon Horman98637e52014-06-20 12:30:16 +0900971}
972
Christopher Faulet61cc8522020-04-20 14:54:42 +0200973/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
974 * NULL if none was found.
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100975 */
Christopher Faulet61cc8522020-04-20 14:54:42 +0200976static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100977{
Christopher Faulet61cc8522020-04-20 14:54:42 +0200978 struct tcpcheck_rule *r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100979
Christopher Faulet61cc8522020-04-20 14:54:42 +0200980 list_for_each_entry(r, rules->list, list) {
981 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
982 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100983 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200984 return NULL;
985}
Cyril Bontéac92a062014-12-27 22:28:38 +0100986
Christopher Faulet61cc8522020-04-20 14:54:42 +0200987/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
988 * NULL if none was found.
989 */
990static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
991{
992 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +0100993
Christopher Faulet61cc8522020-04-20 14:54:42 +0200994 list_for_each_entry_rev(r, rules->list, list) {
995 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
996 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +0100997 }
Christopher Faulet61cc8522020-04-20 14:54:42 +0200998 return NULL;
999}
Cyril Bontéac92a062014-12-27 22:28:38 +01001000
Christopher Faulet61cc8522020-04-20 14:54:42 +02001001/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
1002 * <start> or NULL if non was found. If <start> is NULL, it relies on
1003 * get_first_tcpcheck_rule().
1004 */
1005static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
1006{
1007 struct tcpcheck_rule *r;
Cyril Bontéac92a062014-12-27 22:28:38 +01001008
Christopher Faulet61cc8522020-04-20 14:54:42 +02001009 if (!start)
1010 return get_first_tcpcheck_rule(rules);
Cyril Bontéac92a062014-12-27 22:28:38 +01001011
Christopher Faulet61cc8522020-04-20 14:54:42 +02001012 r = LIST_NEXT(&start->list, typeof(r), list);
1013 list_for_each_entry_from(r, rules->list, list) {
1014 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1015 return r;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001016 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001017 return NULL;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001018}
Simon Horman98637e52014-06-20 12:30:16 +09001019
Simon Horman98637e52014-06-20 12:30:16 +09001020
Christopher Faulet61cc8522020-04-20 14:54:42 +02001021/* Creates info message when a tcp-check healthcheck fails on an expect rule */
1022static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1023 int match, struct ist info)
1024{
1025 struct sample *smp;
Simon Horman98637e52014-06-20 12:30:16 +09001026
Christopher Faulet61cc8522020-04-20 14:54:42 +02001027 /* Follows these step to produce the info message:
1028 * 1. if info field is already provided, copy it
1029 * 2. if the expect rule provides an onerror log-format string,
1030 * use it to produce the message
1031 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1032 * 4. Otherwise produce the generic tcp-check info message
1033 */
1034 if (istlen(info)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02001035 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001036 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001037 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001038 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
1039 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
1040 goto comment;
Cyril Bontéac92a062014-12-27 22:28:38 +01001041 }
Simon Horman98637e52014-06-20 12:30:16 +09001042
Christopher Faulet61cc8522020-04-20 14:54:42 +02001043 if (check->type == PR_O2_TCPCHK_CHK &&
1044 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
1045 goto comment;
Simon Horman98637e52014-06-20 12:30:16 +09001046
Christopher Faulet61cc8522020-04-20 14:54:42 +02001047 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
1048 switch (rule->expect.type) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001049 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02001050 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
1051 break;
1052 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02001053 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02001054 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
Christopher Faulet61cc8522020-04-20 14:54:42 +02001055 tcpcheck_get_step_id(check, rule));
1056 break;
1057 case TCPCHK_EXPECT_BINARY:
1058 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
1059 break;
1060 case TCPCHK_EXPECT_REGEX:
1061 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
1062 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
1063 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
1064 break;
1065 case TCPCHK_EXPECT_REGEX_BINARY:
1066 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001067 break;
1068 case TCPCHK_EXPECT_CUSTOM:
1069 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
1070 break;
1071 case TCPCHK_EXPECT_UNDEF:
1072 /* Should never happen. */
1073 return;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001074 }
1075
Christopher Faulet61cc8522020-04-20 14:54:42 +02001076 comment:
1077 /* If the failing expect rule provides a comment, it is concatenated to
1078 * the info message.
1079 */
1080 if (rule->comment) {
1081 chunk_strcat(msg, " comment: ");
Christopher Faulet88d939c2020-04-22 15:32:11 +02001082 chunk_strcat(msg, rule->comment);
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001083 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001084
Christopher Faulet61cc8522020-04-20 14:54:42 +02001085 /* Finally, the check status code is set if the failing expect rule
1086 * defines a status expression.
1087 */
1088 if (rule->expect.status_expr) {
1089 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1090 rule->expect.status_expr, SMP_T_SINT);
1091 if (smp)
1092 check->code = smp->data.u.sint;
Cyril Bontéac92a062014-12-27 22:28:38 +01001093 }
Simon Horman98637e52014-06-20 12:30:16 +09001094
Christopher Faulet61cc8522020-04-20 14:54:42 +02001095 *(b_tail(msg)) = '\0';
1096}
Cyril Bontéac92a062014-12-27 22:28:38 +01001097
Christopher Faulet61cc8522020-04-20 14:54:42 +02001098/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
1099static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
1100 struct ist info)
1101{
1102 struct sample *smp;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001103
Christopher Faulet61cc8522020-04-20 14:54:42 +02001104 /* Follows these step to produce the info message:
1105 * 1. if info field is already provided, copy it
1106 * 2. if the expect rule provides an onsucces log-format string,
1107 * use it to produce the message
1108 * 3. the expect rule is part of a protcol check (http, redis, mysql...), do nothing
1109 * 4. Otherwise produce the generic tcp-check info message
1110 */
1111 if (istlen(info))
Christopher Fauletb61caf42020-04-21 10:57:42 +02001112 chunk_strncat(msg, istptr(info), istlen(info));
Christopher Faulet61cc8522020-04-20 14:54:42 +02001113 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
1114 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
1115 &rule->expect.onsuccess_fmt);
1116 else if (check->type == PR_O2_TCPCHK_CHK &&
1117 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
1118 chunk_strcat(msg, "(tcp-check)");
Simon Horman98637e52014-06-20 12:30:16 +09001119
Christopher Faulet61cc8522020-04-20 14:54:42 +02001120 /* Finally, the check status code is set if the expect rule defines a
1121 * status expression.
1122 */
1123 if (rule->expect.status_expr) {
1124 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
1125 rule->expect.status_expr, SMP_T_SINT);
1126 if (smp)
1127 check->code = smp->data.u.sint;
Simon Horman98637e52014-06-20 12:30:16 +09001128 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001129
1130 *(b_tail(msg)) = '\0';
Simon Horman98637e52014-06-20 12:30:16 +09001131}
1132
Christopher Faulet61cc8522020-04-20 14:54:42 +02001133/* Builds the server state header used by HTTP health-checks */
1134static int httpchk_build_status_header(struct server *s, struct buffer *buf)
Simon Horman98637e52014-06-20 12:30:16 +09001135{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001136 int sv_state;
1137 int ratio;
1138 char addr[46];
1139 char port[6];
1140 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
1141 "UP %d/%d", "UP",
1142 "NOLB %d/%d", "NOLB",
1143 "no check" };
Simon Horman98637e52014-06-20 12:30:16 +09001144
Christopher Faulet61cc8522020-04-20 14:54:42 +02001145 if (!(s->check.state & CHK_ST_ENABLED))
1146 sv_state = 6;
1147 else if (s->cur_state != SRV_ST_STOPPED) {
1148 if (s->check.health == s->check.rise + s->check.fall - 1)
1149 sv_state = 3; /* UP */
1150 else
1151 sv_state = 2; /* going down */
Simon Horman98637e52014-06-20 12:30:16 +09001152
Christopher Faulet61cc8522020-04-20 14:54:42 +02001153 if (s->cur_state == SRV_ST_STOPPING)
1154 sv_state += 2;
1155 } else {
1156 if (s->check.health)
1157 sv_state = 1; /* going up */
1158 else
1159 sv_state = 0; /* DOWN */
Simon Horman98637e52014-06-20 12:30:16 +09001160 }
Willy Tarreaub7b24782016-06-21 15:32:29 +02001161
Christopher Faulet61cc8522020-04-20 14:54:42 +02001162 chunk_appendf(buf, srv_hlt_st[sv_state],
1163 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
1164 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreaub7b24782016-06-21 15:32:29 +02001165
Christopher Faulet61cc8522020-04-20 14:54:42 +02001166 addr_to_str(&s->addr, addr, sizeof(addr));
1167 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1168 snprintf(port, sizeof(port), "%u", s->svc_port);
1169 else
1170 *port = 0;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001171
Christopher Faulet61cc8522020-04-20 14:54:42 +02001172 chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
1173 addr, port, s->proxy->id, s->id,
1174 global.node,
1175 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1176 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
1177 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
1178 s->nbpend);
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001179
Christopher Faulet61cc8522020-04-20 14:54:42 +02001180 if ((s->cur_state == SRV_ST_STARTING) &&
1181 now.tv_sec < s->last_change + s->slowstart &&
1182 now.tv_sec >= s->last_change) {
1183 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
1184 chunk_appendf(buf, "; throttle=%d%%", ratio);
1185 }
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001186
Christopher Faulet61cc8522020-04-20 14:54:42 +02001187 return b_data(buf);
1188}
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001189
Christopher Faulet61cc8522020-04-20 14:54:42 +02001190/* Internal functions to parse and validate a MySQL packet in the context of an
1191 * expect rule. It start to parse the input buffer at the offset <offset>. If
1192 * <last_read> is set, no more data are expected.
1193 */
1194static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
1195 unsigned int offset, int last_read)
1196{
1197 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1198 enum healthcheck_status status;
1199 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001200 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001201 unsigned int err = 0, plen = 0;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001202
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001203
Christopher Faulet61cc8522020-04-20 14:54:42 +02001204 /* 3 Bytes for the packet length and 1 byte for the sequence id */
1205 if (!last_read && b_data(&check->bi) < offset+4) {
1206 if (!last_read)
1207 goto wait_more_data;
1208
1209 /* invalid length or truncated response */
1210 status = HCHK_STATUS_L7RSP;
1211 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001212 }
1213
Christopher Faulet61cc8522020-04-20 14:54:42 +02001214 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
1215 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
1216 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001217
Christopher Faulet61cc8522020-04-20 14:54:42 +02001218 if (b_data(&check->bi) < offset+plen+4) {
1219 if (!last_read)
1220 goto wait_more_data;
1221
1222 /* invalid length or truncated response */
1223 status = HCHK_STATUS_L7RSP;
1224 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001225 }
Simon Horman98637e52014-06-20 12:30:16 +09001226
Christopher Faulet61cc8522020-04-20 14:54:42 +02001227 if (*b_peek(&check->bi, offset+4) == '\xff') {
1228 /* MySQL Error packet always begin with field_count = 0xff */
1229 status = HCHK_STATUS_L7STS;
1230 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
1231 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
1232 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
1233 goto error;
1234 }
Simon Horman98637e52014-06-20 12:30:16 +09001235
Christopher Faulet61cc8522020-04-20 14:54:42 +02001236 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
1237 /* Not the last rule, continue */
1238 goto out;
1239 }
Simon Horman98637e52014-06-20 12:30:16 +09001240
Christopher Faulet61cc8522020-04-20 14:54:42 +02001241 /* We set the MySQL Version in description for information purpose
1242 * FIXME : it can be cool to use MySQL Version for other purpose,
1243 * like mark as down old MySQL server.
1244 */
1245 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Simon Horman98637e52014-06-20 12:30:16 +09001246
Christopher Faulet61cc8522020-04-20 14:54:42 +02001247 out:
1248 free_trash_chunk(msg);
1249 return ret;
Simon Horman98637e52014-06-20 12:30:16 +09001250
Christopher Faulet61cc8522020-04-20 14:54:42 +02001251 error:
1252 ret = TCPCHK_EVAL_STOP;
1253 check->code = err;
1254 msg = alloc_trash_chunk();
1255 if (msg)
1256 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1257 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1258 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001259
Christopher Faulet61cc8522020-04-20 14:54:42 +02001260 wait_more_data:
1261 ret = TCPCHK_EVAL_WAIT;
1262 goto out;
1263}
Simon Horman98637e52014-06-20 12:30:16 +09001264
Christopher Faulet61cc8522020-04-20 14:54:42 +02001265/* Custom tcp-check expect function to parse and validate the MySQL initial
1266 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1267 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1268 * error occurred.
1269 */
1270static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
1271{
1272 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
1273}
Simon Horman98637e52014-06-20 12:30:16 +09001274
Christopher Faulet61cc8522020-04-20 14:54:42 +02001275/* Custom tcp-check expect function to parse and validate the MySQL OK packet
1276 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
1277 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
1278 * an error occurred.
1279 */
1280static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
1281{
1282 unsigned int hslen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001283
Christopher Faulet61cc8522020-04-20 14:54:42 +02001284 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
1285 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
1286 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
Simon Horman98637e52014-06-20 12:30:16 +09001287
Christopher Faulet61cc8522020-04-20 14:54:42 +02001288 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
1289}
Simon Horman98637e52014-06-20 12:30:16 +09001290
Christopher Faulet61cc8522020-04-20 14:54:42 +02001291/* Custom tcp-check expect function to parse and validate the LDAP bind response
1292 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
1293 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1294 * error occurred.
1295 */
1296static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
1297{
1298 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1299 enum healthcheck_status status;
1300 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001301 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001302 unsigned short msglen = 0;
Simon Horman98637e52014-06-20 12:30:16 +09001303
Christopher Faulet61cc8522020-04-20 14:54:42 +02001304 /* Check if the server speaks LDAP (ASN.1/BER)
1305 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
1306 * http://tools.ietf.org/html/rfc4511
1307 */
1308 /* size of LDAPMessage */
1309 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001310
Christopher Faulet61cc8522020-04-20 14:54:42 +02001311 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
1312 * messageID: 0x02 0x01 0x01: INTEGER 1
1313 * protocolOp: 0x61: bindResponse
1314 */
1315 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
1316 status = HCHK_STATUS_L7RSP;
1317 desc = ist("Not LDAPv3 protocol");
1318 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001319 }
Simon Horman98637e52014-06-20 12:30:16 +09001320
Christopher Faulet61cc8522020-04-20 14:54:42 +02001321 /* size of bindResponse */
1322 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
Simon Horman98637e52014-06-20 12:30:16 +09001323
Christopher Faulet61cc8522020-04-20 14:54:42 +02001324 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1325 * ldapResult: 0x0a 0x01: ENUMERATION
1326 */
1327 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
1328 status = HCHK_STATUS_L7RSP;
1329 desc = ist("Not LDAPv3 protocol");
1330 goto error;
1331 }
Simon Horman98637e52014-06-20 12:30:16 +09001332
Christopher Faulet61cc8522020-04-20 14:54:42 +02001333 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
1334 * resultCode
1335 */
1336 check->code = *(b_head(&check->bi) + msglen + 9);
1337 if (check->code) {
1338 status = HCHK_STATUS_L7STS;
1339 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
1340 goto error;
Simon Horman98637e52014-06-20 12:30:16 +09001341 }
1342
Christopher Faulet61cc8522020-04-20 14:54:42 +02001343 set_server_check_status(check, rule->expect.ok_status, "Success");
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001344
Christopher Faulet61cc8522020-04-20 14:54:42 +02001345 out:
1346 free_trash_chunk(msg);
1347 return ret;
1348
1349 error:
1350 ret = TCPCHK_EVAL_STOP;
1351 msg = alloc_trash_chunk();
1352 if (msg)
1353 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1354 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1355 goto out;
1356
1357 wait_more_data:
1358 ret = TCPCHK_EVAL_WAIT;
1359 goto out;
Simon Horman98637e52014-06-20 12:30:16 +09001360}
1361
Christopher Faulet61cc8522020-04-20 14:54:42 +02001362/* Custom tcp-check expect function to parse and validate the SPOP hello agent
1363 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1364 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
Simon Horman98637e52014-06-20 12:30:16 +09001365 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001366static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001367{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001368 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1369 enum healthcheck_status status;
1370 struct buffer *msg = NULL;
Christopher Fauletb61caf42020-04-21 10:57:42 +02001371 struct ist desc = IST_NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001372 unsigned int framesz;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001373
Willy Tarreaubaaee002006-06-26 02:48:02 +02001374
Christopher Faulet61cc8522020-04-20 14:54:42 +02001375 memcpy(&framesz, b_head(&check->bi), 4);
1376 framesz = ntohl(framesz);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001377
Christopher Faulet61cc8522020-04-20 14:54:42 +02001378 if (!last_read && b_data(&check->bi) < (4+framesz))
1379 goto wait_more_data;
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001380
Christopher Faulet61cc8522020-04-20 14:54:42 +02001381 memset(b_orig(&trash), 0, b_size(&trash));
1382 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
1383 status = HCHK_STATUS_L7RSP;
1384 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
1385 goto error;
1386 }
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001387
Christopher Faulet61cc8522020-04-20 14:54:42 +02001388 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Willy Tarreaubaaee002006-06-26 02:48:02 +02001389
Christopher Faulet61cc8522020-04-20 14:54:42 +02001390 out:
1391 free_trash_chunk(msg);
1392 return ret;
Christopher Fauleta32a2502020-04-20 09:04:37 +02001393
Christopher Faulet61cc8522020-04-20 14:54:42 +02001394 error:
1395 ret = TCPCHK_EVAL_STOP;
1396 msg = alloc_trash_chunk();
1397 if (msg)
1398 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1399 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1400 goto out;
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001401
Christopher Faulet61cc8522020-04-20 14:54:42 +02001402 wait_more_data:
1403 ret = TCPCHK_EVAL_WAIT;
1404 goto out;
1405}
Willy Tarreaubaaee002006-06-26 02:48:02 +02001406
Christopher Faulet61cc8522020-04-20 14:54:42 +02001407/* Custom tcp-check expect function to parse and validate the agent-check
1408 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
1409 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
1410 */
1411static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
1412{
1413 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
1414 enum healthcheck_status status = HCHK_STATUS_CHECKED;
1415 const char *hs = NULL; /* health status */
1416 const char *as = NULL; /* admin status */
1417 const char *ps = NULL; /* performance status */
1418 const char *cs = NULL; /* maxconn */
1419 const char *err = NULL; /* first error to report */
1420 const char *wrn = NULL; /* first warning to report */
1421 char *cmd, *p;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001422
Christopher Faulet61cc8522020-04-20 14:54:42 +02001423 /* We're getting an agent check response. The agent could
1424 * have been disabled in the mean time with a long check
1425 * still pending. It is important that we ignore the whole
1426 * response.
1427 */
1428 if (!(check->state & CHK_ST_ENABLED))
1429 goto out;
Willy Tarreauf1503172012-09-28 19:39:36 +02001430
Christopher Faulet61cc8522020-04-20 14:54:42 +02001431 /* The agent supports strings made of a single line ended by the
1432 * first CR ('\r') or LF ('\n'). This line is composed of words
1433 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
1434 * line may optionally contained a description of a state change
1435 * after a sharp ('#'), which is only considered if a health state
1436 * is announced.
1437 *
1438 * Words may be composed of :
1439 * - a numeric weight suffixed by the percent character ('%').
1440 * - a health status among "up", "down", "stopped", and "fail".
1441 * - an admin status among "ready", "drain", "maint".
1442 *
1443 * These words may appear in any order. If multiple words of the
1444 * same category appear, the last one wins.
1445 */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001446
Christopher Faulet61cc8522020-04-20 14:54:42 +02001447 p = b_head(&check->bi);
1448 while (*p && *p != '\n' && *p != '\r')
1449 p++;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001450
Christopher Faulet61cc8522020-04-20 14:54:42 +02001451 if (!*p) {
1452 if (!last_read)
1453 goto wait_more_data;
1454
1455 /* at least inform the admin that the agent is mis-behaving */
1456 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
1457 goto out;
1458 }
1459
1460 *p = 0;
1461 cmd = b_head(&check->bi);
1462
1463 while (*cmd) {
1464 /* look for next word */
1465 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
1466 cmd++;
1467 continue;
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001468 }
1469
Christopher Faulet61cc8522020-04-20 14:54:42 +02001470 if (*cmd == '#') {
1471 /* this is the beginning of a health status description,
1472 * skip the sharp and blanks.
Willy Tarreau86eded62019-06-14 14:47:49 +02001473 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001474 cmd++;
1475 while (*cmd == '\t' || *cmd == ' ')
1476 cmd++;
1477 break;
Willy Tarreau00149122017-10-04 18:05:01 +02001478 }
1479
Christopher Faulet61cc8522020-04-20 14:54:42 +02001480 /* find the end of the word so that we have a null-terminated
1481 * word between <cmd> and <p>.
1482 */
1483 p = cmd + 1;
1484 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
1485 p++;
1486 if (*p)
1487 *p++ = 0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001488
Christopher Faulet61cc8522020-04-20 14:54:42 +02001489 /* first, health statuses */
1490 if (strcasecmp(cmd, "up") == 0) {
1491 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
1492 status = HCHK_STATUS_L7OKD;
1493 hs = cmd;
1494 }
1495 else if (strcasecmp(cmd, "down") == 0) {
1496 check->server->check.health = 0;
1497 status = HCHK_STATUS_L7STS;
1498 hs = cmd;
1499 }
1500 else if (strcasecmp(cmd, "stopped") == 0) {
1501 check->server->check.health = 0;
1502 status = HCHK_STATUS_L7STS;
1503 hs = cmd;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001504 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001505 else if (strcasecmp(cmd, "fail") == 0) {
1506 check->server->check.health = 0;
1507 status = HCHK_STATUS_L7STS;
1508 hs = cmd;
1509 }
1510 /* admin statuses */
1511 else if (strcasecmp(cmd, "ready") == 0) {
1512 as = cmd;
1513 }
1514 else if (strcasecmp(cmd, "drain") == 0) {
1515 as = cmd;
1516 }
1517 else if (strcasecmp(cmd, "maint") == 0) {
1518 as = cmd;
1519 }
1520 /* try to parse a weight here and keep the last one */
1521 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
1522 ps = cmd;
1523 }
1524 /* try to parse a maxconn here */
1525 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
1526 cs = cmd;
1527 }
1528 else {
1529 /* keep a copy of the first error */
1530 if (!err)
1531 err = cmd;
1532 }
1533 /* skip to next word */
1534 cmd = p;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001535 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001536 /* here, cmd points either to \0 or to the beginning of a
1537 * description. Skip possible leading spaces.
1538 */
1539 while (*cmd == ' ' || *cmd == '\n')
1540 cmd++;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001541
Christopher Faulet61cc8522020-04-20 14:54:42 +02001542 /* First, update the admin status so that we avoid sending other
1543 * possibly useless warnings and can also update the health if
1544 * present after going back up.
1545 */
1546 if (as) {
1547 if (strcasecmp(as, "drain") == 0)
1548 srv_adm_set_drain(check->server);
1549 else if (strcasecmp(as, "maint") == 0)
1550 srv_adm_set_maint(check->server);
1551 else
1552 srv_adm_set_ready(check->server);
1553 }
Simon Horman98637e52014-06-20 12:30:16 +09001554
Christopher Faulet61cc8522020-04-20 14:54:42 +02001555 /* now change weights */
1556 if (ps) {
1557 const char *msg;
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001558
Christopher Faulet61cc8522020-04-20 14:54:42 +02001559 msg = server_parse_weight_change_request(check->server, ps);
1560 if (!wrn || !*wrn)
1561 wrn = msg;
1562 }
Simon Horman98637e52014-06-20 12:30:16 +09001563
Christopher Faulet61cc8522020-04-20 14:54:42 +02001564 if (cs) {
1565 const char *msg;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001566
Christopher Faulet61cc8522020-04-20 14:54:42 +02001567 cs += strlen("maxconn:");
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001568
Christopher Faulet61cc8522020-04-20 14:54:42 +02001569 msg = server_parse_maxconn_change_request(check->server, cs);
1570 if (!wrn || !*wrn)
1571 wrn = msg;
Simon Horman5c942422013-11-25 10:46:32 +09001572 }
1573
Christopher Faulet61cc8522020-04-20 14:54:42 +02001574 /* and finally health status */
1575 if (hs) {
1576 /* We'll report some of the warnings and errors we have
1577 * here. Down reports are critical, we leave them untouched.
1578 * Lack of report, or report of 'UP' leaves the room for
1579 * ERR first, then WARN.
1580 */
1581 const char *msg = cmd;
1582 struct buffer *t;
Simon Horman5c942422013-11-25 10:46:32 +09001583
Christopher Faulet61cc8522020-04-20 14:54:42 +02001584 if (!*msg || status == HCHK_STATUS_L7OKD) {
1585 if (err && *err)
1586 msg = err;
1587 else if (wrn && *wrn)
1588 msg = wrn;
1589 }
Willy Tarreau1746eec2014-04-25 10:46:47 +02001590
Christopher Faulet61cc8522020-04-20 14:54:42 +02001591 t = get_trash_chunk();
1592 chunk_printf(t, "via agent : %s%s%s%s",
1593 hs, *msg ? " (" : "",
1594 msg, *msg ? ")" : "");
1595 set_server_check_status(check, status, t->area);
1596 }
1597 else if (err && *err) {
1598 /* No status change but we'd like to report something odd.
1599 * Just report the current state and copy the message.
1600 */
1601 chunk_printf(&trash, "agent reports an error : %s", err);
1602 set_server_check_status(check, status/*check->status*/, trash.area);
1603 }
1604 else if (wrn && *wrn) {
1605 /* No status change but we'd like to report something odd.
1606 * Just report the current state and copy the message.
1607 */
1608 chunk_printf(&trash, "agent warns : %s", wrn);
1609 set_server_check_status(check, status/*check->status*/, trash.area);
1610 }
1611 else
1612 set_server_check_status(check, status, NULL);
Willy Tarreau1746eec2014-04-25 10:46:47 +02001613
Christopher Faulet61cc8522020-04-20 14:54:42 +02001614 out:
1615 return ret;
Simon Horman5c942422013-11-25 10:46:32 +09001616
Christopher Faulet61cc8522020-04-20 14:54:42 +02001617 wait_more_data:
1618 ret = TCPCHK_EVAL_WAIT;
1619 goto out;
Simon Horman5c942422013-11-25 10:46:32 +09001620}
1621
Christopher Faulet61cc8522020-04-20 14:54:42 +02001622/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1623 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1624 * TCPCHK_EVAL_STOP if an error occurred.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001625 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001626static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Willy Tarreau865c5142016-12-21 20:04:48 +01001627{
Christopher Faulet61cc8522020-04-20 14:54:42 +02001628 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1629 struct tcpcheck_connect *connect = &rule->connect;
1630 struct proxy *proxy = check->proxy;
1631 struct server *s = check->server;
1632 struct task *t = check->task;
1633 struct conn_stream *cs;
1634 struct connection *conn = NULL;
1635 struct protocol *proto;
1636 struct xprt_ops *xprt;
1637 int status, port;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001638
Christopher Faulet61cc8522020-04-20 14:54:42 +02001639 /* For a connect action we'll create a new connection. We may also have
1640 * to kill a previous one. But we don't want to leave *without* a
1641 * connection if we came here from the connection layer, hence with a
1642 * connection. Thus we'll proceed in the following order :
1643 * 1: close but not release previous connection (handled by the caller)
1644 * 2: try to get a new connection
1645 * 3: release and replace the old one on success
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02001646 */
Willy Tarreaue7b73482013-11-21 11:50:50 +01001647
Christopher Faulet61cc8522020-04-20 14:54:42 +02001648 /* 2- prepare new connection */
1649 cs = cs_new(NULL);
1650 if (!cs) {
1651 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1652 tcpcheck_get_step_id(check, rule));
1653 if (rule->comment)
1654 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1655 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1656 ret = TCPCHK_EVAL_STOP;
1657 goto out;
1658 }
Willy Tarreau15f39102013-12-11 20:41:18 +01001659
Christopher Faulet61cc8522020-04-20 14:54:42 +02001660 /* 3- release and replace the old one on success */
1661 if (check->cs) {
1662 if (check->wait_list.events)
Christopher Faulet06150e42020-04-27 11:22:56 +02001663 check->cs->conn->mux->unsubscribe(check->cs, check->wait_list.events,
1664 &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02001665
1666 /* We may have been scheduled to run, and the I/O handler
1667 * expects to have a cs, so remove the tasklet
1668 */
1669 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
1670 cs_destroy(check->cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001671 }
1672
Christopher Faulet61cc8522020-04-20 14:54:42 +02001673 tasklet_set_tid(check->wait_list.tasklet, tid);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001674
Christopher Faulet61cc8522020-04-20 14:54:42 +02001675 check->cs = cs;
1676 conn = cs->conn;
1677 conn_set_owner(conn, check->sess, NULL);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001678
Christopher Faulet61cc8522020-04-20 14:54:42 +02001679 /* Maybe there were an older connection we were waiting on */
1680 check->wait_list.events = 0;
1681 conn->target = s ? &s->obj_type : &proxy->obj_type;
1682
1683 /* no client address */
1684 if (!sockaddr_alloc(&conn->dst)) {
1685 status = SF_ERR_RESOURCE;
1686 goto fail_check;
1687 }
1688
1689 /* connect to the connect rule addr if specified, otherwise the check
1690 * addr if specified on the server. otherwise, use the server addr
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001691 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02001692 *conn->dst = (is_addr(&connect->addr)
1693 ? connect->addr
1694 : (is_addr(&check->addr) ? check->addr : s->addr));
1695 proto = protocol_by_family(conn->dst->ss_family);
Simon Horman98637e52014-06-20 12:30:16 +09001696
Christopher Faulet61cc8522020-04-20 14:54:42 +02001697 port = 0;
1698 if (!port && connect->port)
1699 port = connect->port;
1700 if (!port && connect->port_expr) {
1701 struct sample *smp;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001702
Christopher Faulet61cc8522020-04-20 14:54:42 +02001703 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1704 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1705 connect->port_expr, SMP_T_SINT);
1706 if (smp)
1707 port = smp->data.u.sint;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001708 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001709 if (!port && is_inet_addr(&connect->addr))
1710 port = get_host_port(&connect->addr);
1711 if (!port && check->port)
1712 port = check->port;
1713 if (!port && is_inet_addr(&check->addr))
1714 port = get_host_port(&check->addr);
1715 if (!port)
1716 port = s->svc_port;
1717 set_host_port(conn->dst, port);
Willy Tarreau213c6782014-10-02 14:51:02 +02001718
Christopher Faulet61cc8522020-04-20 14:54:42 +02001719 xprt = ((connect->options & TCPCHK_OPT_SSL)
1720 ? xprt_get(XPRT_SSL)
1721 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001722
Christopher Faulet61cc8522020-04-20 14:54:42 +02001723 conn_prepare(conn, proto, xprt);
1724 cs_attach(cs, check, &check_conn_cb);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001725
Christopher Faulet61cc8522020-04-20 14:54:42 +02001726 status = SF_ERR_INTERNAL;
1727 if (proto && proto->connect) {
1728 struct tcpcheck_rule *next;
1729 int flags = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02001730
Christopher Faulet61cc8522020-04-20 14:54:42 +02001731 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1732 flags |= CONNECT_HAS_DATA;
Christopher Faulet206368d2020-04-03 14:51:06 +02001733
Christopher Faulet61cc8522020-04-20 14:54:42 +02001734 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1735 if (!next || next->action != TCPCHK_ACT_EXPECT)
1736 flags |= CONNECT_DELACK_ALWAYS;
1737 status = proto->connect(conn, flags);
Christopher Faulet206368d2020-04-03 14:51:06 +02001738 }
1739
Christopher Faulet61cc8522020-04-20 14:54:42 +02001740 if (status != SF_ERR_NONE)
1741 goto fail_check;
Christopher Faulet799f3a42020-04-07 12:06:14 +02001742
Christopher Faulet61cc8522020-04-20 14:54:42 +02001743 conn->flags |= CO_FL_PRIVATE;
1744 conn->ctx = cs;
Christopher Faulet206368d2020-04-03 14:51:06 +02001745
Christopher Faulet61cc8522020-04-20 14:54:42 +02001746 /* The mux may be initialized now if there isn't server attached to the
1747 * check (email alerts) or if there is a mux proto specified or if there
1748 * is no alpn.
1749 */
Christopher Faulet12882cf2020-04-23 15:50:18 +02001750 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1751 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001752 const struct mux_ops *mux_ops;
Christopher Faulet206368d2020-04-03 14:51:06 +02001753
Christopher Faulet61cc8522020-04-20 14:54:42 +02001754 if (connect->mux_proto)
1755 mux_ops = connect->mux_proto->mux;
Christopher Faulet12882cf2020-04-23 15:50:18 +02001756 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
Christopher Faulet61cc8522020-04-20 14:54:42 +02001757 mux_ops = check->mux_proto->mux;
1758 else {
1759 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1760 ? PROTO_MODE_HTTP
1761 : PROTO_MODE_TCP);
1762
1763 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
Christopher Faulet206368d2020-04-03 14:51:06 +02001764 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001765 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1766 status = SF_ERR_INTERNAL;
1767 goto fail_check;
1768 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001769 }
1770
Christopher Faulet61cc8522020-04-20 14:54:42 +02001771#ifdef USE_OPENSSL
1772 if (connect->sni)
1773 ssl_sock_set_servername(conn, connect->sni);
1774 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
1775 ssl_sock_set_servername(conn, s->check.sni);
1776
1777 if (connect->alpn)
1778 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1779 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
1780 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1781#endif
1782 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
1783 conn->send_proxy_ofs = 1;
1784 conn->flags |= CO_FL_SOCKS4;
1785 }
1786 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1787 conn->send_proxy_ofs = 1;
1788 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet206368d2020-04-03 14:51:06 +02001789 }
1790
Christopher Faulet61cc8522020-04-20 14:54:42 +02001791 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1792 conn->send_proxy_ofs = 1;
1793 conn->flags |= CO_FL_SEND_PROXY;
1794 }
1795 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1796 conn->send_proxy_ofs = 1;
1797 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet206368d2020-04-03 14:51:06 +02001798 }
1799
Christopher Faulet61cc8522020-04-20 14:54:42 +02001800 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1801 /* Some servers don't like reset on close */
1802 fdtab[cs->conn->handle.fd].linger_risk = 0;
1803 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001804
Christopher Faulet61cc8522020-04-20 14:54:42 +02001805 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1806 if (xprt_add_hs(conn) < 0)
1807 status = SF_ERR_RESOURCE;
1808 }
Christopher Faulet206368d2020-04-03 14:51:06 +02001809
Christopher Faulet61cc8522020-04-20 14:54:42 +02001810 fail_check:
1811 /* It can return one of :
1812 * - SF_ERR_NONE if everything's OK
1813 * - SF_ERR_SRVTO if there are no more servers
1814 * - SF_ERR_SRVCL if the connection was refused by the server
1815 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1816 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1817 * - SF_ERR_INTERNAL for any other purely internal errors
1818 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1819 * Note that we try to prevent the network stack from sending the ACK during the
1820 * connect() when a pure TCP check is used (without PROXY protocol).
1821 */
1822 switch (status) {
1823 case SF_ERR_NONE:
1824 /* we allow up to min(inter, timeout.connect) for a connection
1825 * to establish but only when timeout.check is set as it may be
1826 * to short for a full check otherwise
1827 */
1828 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Christopher Faulet206368d2020-04-03 14:51:06 +02001829
Christopher Faulet61cc8522020-04-20 14:54:42 +02001830 if (proxy->timeout.check && proxy->timeout.connect) {
1831 int t_con = tick_add(now_ms, proxy->timeout.connect);
1832 t->expire = tick_first(t->expire, t_con);
1833 }
1834 break;
1835 case SF_ERR_SRVTO: /* ETIMEDOUT */
1836 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1837 case SF_ERR_PRXCOND:
1838 case SF_ERR_RESOURCE:
1839 case SF_ERR_INTERNAL:
1840 chk_report_conn_err(check, errno, 0);
1841 ret = TCPCHK_EVAL_STOP;
1842 goto out;
Christopher Faulet206368d2020-04-03 14:51:06 +02001843 }
1844
Christopher Faulet61cc8522020-04-20 14:54:42 +02001845 /* don't do anything until the connection is established */
1846 if (conn->flags & CO_FL_WAIT_XPRT) {
1847 ret = TCPCHK_EVAL_WAIT;
1848 goto out;
1849 }
1850
1851 out:
1852 if (conn && check->result == CHK_RES_FAILED)
1853 conn->flags |= CO_FL_ERROR;
1854 return ret;
Christopher Faulet206368d2020-04-03 14:51:06 +02001855}
1856
Christopher Faulet61cc8522020-04-20 14:54:42 +02001857/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1858 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1859 * TCPCHK_EVAL_STOP if an error occurred.
1860 */
1861static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001862{
1863 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02001864 struct tcpcheck_send *send = &rule->send;
1865 struct conn_stream *cs = check->cs;
1866 struct connection *conn = cs_conn(cs);
1867 struct buffer *tmp = NULL;
1868 struct htx *htx = NULL;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001869
Christopher Faulet61cc8522020-04-20 14:54:42 +02001870 /* reset the read & write buffer */
1871 b_reset(&check->bi);
1872 b_reset(&check->bo);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001873
Christopher Faulet61cc8522020-04-20 14:54:42 +02001874 switch (send->type) {
1875 case TCPCHK_SEND_STRING:
1876 case TCPCHK_SEND_BINARY:
1877 if (istlen(send->data) >= b_size(&check->bo)) {
1878 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1879 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1880 tcpcheck_get_step_id(check, rule));
1881 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1882 ret = TCPCHK_EVAL_STOP;
1883 goto out;
1884 }
1885 b_putist(&check->bo, send->data);
1886 break;
1887 case TCPCHK_SEND_STRING_LF:
1888 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1889 if (!b_data(&check->bo))
1890 goto out;
1891 break;
1892 case TCPCHK_SEND_BINARY_LF:
1893 tmp = alloc_trash_chunk();
1894 if (!tmp)
1895 goto error_lf;
1896 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1897 if (!b_data(tmp))
1898 goto out;
1899 tmp->area[tmp->data] = '\0';
1900 b_set_data(&check->bo, b_size(&check->bo));
1901 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
1902 goto error_lf;
1903 break;
1904 case TCPCHK_SEND_HTTP: {
1905 struct htx_sl *sl;
1906 struct ist meth, uri, vsn, clen, body;
1907 unsigned int slflags = 0;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001908
Christopher Faulet61cc8522020-04-20 14:54:42 +02001909 tmp = alloc_trash_chunk();
1910 if (!tmp)
1911 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001912
Christopher Faulet61cc8522020-04-20 14:54:42 +02001913 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1914 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1915 : http_known_methods[send->http.meth.meth]);
1916 uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
1917 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001918
Christopher Faulet61cc8522020-04-20 14:54:42 +02001919 if (istlen(vsn) == 8 &&
1920 (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1')))
1921 slflags |= HTX_SL_F_VER_11;
1922 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1923 if (!isttest(send->http.body))
1924 slflags |= HTX_SL_F_BODYLESS;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001925
Christopher Faulet61cc8522020-04-20 14:54:42 +02001926 htx = htx_from_buf(&check->bo);
1927 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1928 if (!sl)
1929 goto error_htx;
1930 sl->info.req.meth = send->http.meth.meth;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001931
Christopher Faulet61cc8522020-04-20 14:54:42 +02001932 body = send->http.body; // TODO: handle body_fmt
1933 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001934
Christopher Faulet61cc8522020-04-20 14:54:42 +02001935 if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
1936 !htx_add_header(htx, ist("Content-length"), clen))
1937 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001938
Christopher Faulet61cc8522020-04-20 14:54:42 +02001939 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1940 struct tcpcheck_http_hdr *hdr;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001941
Christopher Faulet61cc8522020-04-20 14:54:42 +02001942 list_for_each_entry(hdr, &send->http.hdrs, list) {
1943 chunk_reset(tmp);
1944 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1945 if (!b_data(tmp))
1946 continue;
1947 if (!htx_add_header(htx, hdr->name, ist2(b_orig(tmp), b_data(tmp))))
1948 goto error_htx;
1949 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001950
Christopher Faulet61cc8522020-04-20 14:54:42 +02001951 }
1952 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1953 chunk_reset(tmp);
1954 httpchk_build_status_header(check->server, tmp);
1955 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1956 goto error_htx;
1957 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001958
Christopher Faulet61cc8522020-04-20 14:54:42 +02001959 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
1960 (istlen(body) && !htx_add_data_atonce(htx, send->http.body)) ||
1961 !htx_add_endof(htx, HTX_BLK_EOM))
1962 goto error_htx;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001963
Christopher Faulet61cc8522020-04-20 14:54:42 +02001964 htx_to_buf(htx, &check->bo);
1965 break;
1966 }
1967 case TCPCHK_SEND_UNDEF:
1968 /* Should never happen. */
1969 ret = TCPCHK_EVAL_STOP;
1970 goto out;
1971 };
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001972
Christopher Faulet6d471212020-04-22 11:09:25 +02001973
1974 if (conn->mux->snd_buf(cs, &check->bo,
1975 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Faulet815516d2020-04-21 13:02:14 +02001976 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001977 ret = TCPCHK_EVAL_STOP;
Christopher Faulet815516d2020-04-21 13:02:14 +02001978 goto out;
1979 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02001980 }
Christopher Faulet6d471212020-04-22 11:09:25 +02001981 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02001982 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1983 ret = TCPCHK_EVAL_WAIT;
1984 goto out;
1985 }
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001986
Christopher Faulet61cc8522020-04-20 14:54:42 +02001987 out:
1988 free_trash_chunk(tmp);
1989 return ret;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02001990
Christopher Faulet61cc8522020-04-20 14:54:42 +02001991 error_htx:
1992 if (htx) {
1993 htx_reset(htx);
1994 htx_to_buf(htx, &check->bo);
1995 }
1996 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1997 tcpcheck_get_step_id(check, rule));
1998 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1999 ret = TCPCHK_EVAL_STOP;
2000 goto out;
2001
2002 error_lf:
2003 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
2004 tcpcheck_get_step_id(check, rule));
2005 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2006 ret = TCPCHK_EVAL_STOP;
2007 goto out;
2008
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002009}
2010
Christopher Faulet61cc8522020-04-20 14:54:42 +02002011/* Try to reveice data before evaluting a tcp-check expect rule. Returns
2012 * TCPCHK_EVAL_WAIT if it is already subcribed on receive events or if nothing
2013 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
2014 * TCPCHK_EVAL_STOP if an error occurred.
2015 */
2016static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
Christopher Faulet1997eca2020-04-03 23:13:50 +02002017{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002018 struct conn_stream *cs = check->cs;
2019 struct connection *conn = cs_conn(cs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02002020 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002021 size_t max, read, cur_read = 0;
2022 int is_empty;
2023 int read_poll = MAX_READ_POLL_LOOPS;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002024
Christopher Faulet61cc8522020-04-20 14:54:42 +02002025 if (check->wait_list.events & SUB_RETRY_RECV)
2026 goto wait_more_data;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002027
Christopher Faulet61cc8522020-04-20 14:54:42 +02002028 if (cs->flags & CS_FL_EOS)
2029 goto end_recv;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002030
Christopher Faulet61cc8522020-04-20 14:54:42 +02002031 /* errors on the connection and the conn-stream were already checked */
Christopher Faulet1997eca2020-04-03 23:13:50 +02002032
Christopher Faulet61cc8522020-04-20 14:54:42 +02002033 /* prepare to detect if the mux needs more room */
2034 cs->flags &= ~CS_FL_WANT_ROOM;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002035
Christopher Faulet61cc8522020-04-20 14:54:42 +02002036 while ((cs->flags & CS_FL_RCV_MORE) ||
2037 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
2038 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
2039 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
2040 cur_read += read;
2041 if (!read ||
2042 (cs->flags & CS_FL_WANT_ROOM) ||
2043 (--read_poll <= 0) ||
2044 (read < max && read >= global.tune.recv_enough))
2045 break;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002046 }
2047
Christopher Faulet61cc8522020-04-20 14:54:42 +02002048 end_recv:
2049 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
2050 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
2051 /* Report network errors only if we got no other data. Otherwise
2052 * we'll let the upper layers decide whether the response is OK
2053 * or not. It is very common that an RST sent by the server is
2054 * reported as an error just after the last data chunk.
2055 */
2056 goto stop;
2057 }
2058 if (!cur_read) {
2059 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
2060 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2061 goto wait_more_data;
2062 }
2063 if (is_empty) {
2064 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
2065 tcpcheck_get_step_id(check, rule));
2066 if (rule->comment)
2067 chunk_appendf(&trash, " comment: '%s'", rule->comment);
2068 set_server_check_status(check, rule->expect.err_status, trash.area);
2069 goto stop;
2070 }
2071 }
Christopher Faulet1997eca2020-04-03 23:13:50 +02002072
2073 out:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002074 return ret;
2075
Christopher Faulet61cc8522020-04-20 14:54:42 +02002076 stop:
Christopher Faulet1997eca2020-04-03 23:13:50 +02002077 ret = TCPCHK_EVAL_STOP;
Christopher Faulet1997eca2020-04-03 23:13:50 +02002078 goto out;
2079
2080 wait_more_data:
2081 ret = TCPCHK_EVAL_WAIT;
2082 goto out;
2083}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002084
Christopher Faulet61cc8522020-04-20 14:54:42 +02002085/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
2086 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
2087 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
2088 * error occurred.
2089 */
2090static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
Christopher Faulet267b01b2020-04-04 10:27:09 +02002091{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002092 struct htx *htx = htxbuf(&check->bi);
2093 struct htx_sl *sl;
2094 struct htx_blk *blk;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002095 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002096 struct tcpcheck_expect *expect = &rule->expect;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002097 struct buffer *msg = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002098 enum healthcheck_status status;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002099 struct ist desc = IST_NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002100 int i, match, inverse;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002101
Christopher Faulet61cc8522020-04-20 14:54:42 +02002102 last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
Christopher Faulet267b01b2020-04-04 10:27:09 +02002103
Christopher Faulet61cc8522020-04-20 14:54:42 +02002104 if (htx->flags & HTX_FL_PARSING_ERROR) {
2105 status = HCHK_STATUS_L7RSP;
2106 goto error;
2107 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002108
Christopher Faulet61cc8522020-04-20 14:54:42 +02002109 if (htx_is_empty(htx)) {
2110 if (last_read) {
2111 status = HCHK_STATUS_L7RSP;
2112 goto error;
2113 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002114 goto wait_more_data;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002115 }
Christopher Faulet267b01b2020-04-04 10:27:09 +02002116
Christopher Faulet61cc8522020-04-20 14:54:42 +02002117 sl = http_get_stline(htx);
2118 check->code = sl->info.res.status;
2119
2120 if (check->server &&
2121 (check->server->proxy->options & PR_O_DISABLE404) &&
2122 (check->server->next_state != SRV_ST_STOPPED) &&
2123 (check->code == 404)) {
2124 /* 404 may be accepted as "stopping" only if the server was up */
2125 goto out;
2126 }
2127
2128 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2129 /* Make GCC happy ; initialize match to a failure state. */
2130 match = inverse;
2131
2132 switch (expect->type) {
2133 case TCPCHK_EXPECT_HTTP_STATUS:
Christopher Faulet8021a5f2020-04-24 13:53:12 +02002134 match = 0;
2135 for (i = 0; i < expect->codes.num; i++) {
2136 if (sl->info.res.status >= expect->codes.codes[i][0] &&
2137 sl->info.res.status <= expect->codes.codes[i][1]) {
2138 match = 1;
2139 break;
2140 }
2141 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002142
2143 /* Set status and description in case of error */
2144 status = HCHK_STATUS_L7STS;
2145 desc = htx_sl_res_reason(sl);
2146 break;
2147 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
2148 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
2149
2150 /* Set status and description in case of error */
2151 status = HCHK_STATUS_L7STS;
2152 desc = htx_sl_res_reason(sl);
2153 break;
2154
2155 case TCPCHK_EXPECT_HTTP_BODY:
2156 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
2157 chunk_reset(&trash);
2158 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
2159 enum htx_blk_type type = htx_get_blk_type(blk);
2160
2161 if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
2162 break;
2163 if (type == HTX_BLK_DATA) {
2164 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
2165 break;
2166 }
2167 }
2168
2169 if (!b_data(&trash)) {
2170 if (!last_read)
2171 goto wait_more_data;
2172 status = HCHK_STATUS_L7RSP;
2173 desc = ist("HTTP content check could not find a response body");
2174 goto error;
2175 }
2176
2177 if (!last_read &&
2178 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
2179 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
2180 ret = TCPCHK_EVAL_WAIT;
2181 goto out;
2182 }
2183
2184 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002185 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002186 else
2187 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
2188
2189 /* Set status and description in case of error */
Christopher Faulet267b01b2020-04-04 10:27:09 +02002190 status = HCHK_STATUS_L7RSP;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002191 desc = (inverse
2192 ? ist("HTTP check matched unwanted content")
2193 : ist("HTTP content check did not match"));
2194 break;
2195
2196 default:
2197 /* should never happen */
2198 status = HCHK_STATUS_L7RSP;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002199 goto error;
2200 }
2201
Christopher Faulet61cc8522020-04-20 14:54:42 +02002202 /* Wait for more data on mismatch only if no minimum is defined (-1),
2203 * otherwise the absence of match is already conclusive.
2204 */
2205 if (!match && !last_read && (expect->min_recv == -1)) {
2206 ret = TCPCHK_EVAL_WAIT;
2207 goto out;
2208 }
2209
2210 if (!(match ^ inverse))
2211 goto error;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002212
2213 out:
2214 free_trash_chunk(msg);
2215 return ret;
2216
2217 error:
2218 ret = TCPCHK_EVAL_STOP;
2219 msg = alloc_trash_chunk();
2220 if (msg)
Christopher Faulet61cc8522020-04-20 14:54:42 +02002221 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
Christopher Faulet267b01b2020-04-04 10:27:09 +02002222 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2223 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002224
2225 wait_more_data:
2226 ret = TCPCHK_EVAL_WAIT;
2227 goto out;
2228}
2229
Christopher Faulet61cc8522020-04-20 14:54:42 +02002230/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
2231 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
2232 * if an error occurred.
2233 */
2234static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
2235{
2236 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2237 struct tcpcheck_expect *expect = &rule->expect;
2238 struct buffer *msg = NULL;
2239 int match, inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002240
Christopher Faulet61cc8522020-04-20 14:54:42 +02002241 last_read |= b_full(&check->bi);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002242
Christopher Faulet61cc8522020-04-20 14:54:42 +02002243 /* The current expect might need more data than the previous one, check again
2244 * that the minimum amount data required to match is respected.
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002245 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002246 if (!last_read) {
2247 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
2248 (b_data(&check->bi) < istlen(expect->data))) {
2249 ret = TCPCHK_EVAL_WAIT;
2250 goto out;
2251 }
2252 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
2253 ret = TCPCHK_EVAL_WAIT;
2254 goto out;
2255 }
2256 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002257
Christopher Faulet61cc8522020-04-20 14:54:42 +02002258 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2259 /* Make GCC happy ; initialize match to a failure state. */
2260 match = inverse;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002261
Christopher Faulet61cc8522020-04-20 14:54:42 +02002262 switch (expect->type) {
2263 case TCPCHK_EXPECT_STRING:
2264 case TCPCHK_EXPECT_BINARY:
Christopher Fauletb61caf42020-04-21 10:57:42 +02002265 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002266 break;
2267 case TCPCHK_EXPECT_REGEX:
Christopher Faulet88d939c2020-04-22 15:32:11 +02002268 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002269 break;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002270
Christopher Faulet61cc8522020-04-20 14:54:42 +02002271 case TCPCHK_EXPECT_REGEX_BINARY:
2272 chunk_reset(&trash);
2273 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet88d939c2020-04-22 15:32:11 +02002274 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
Christopher Faulet61cc8522020-04-20 14:54:42 +02002275 break;
2276 case TCPCHK_EXPECT_CUSTOM:
2277 if (expect->custom)
2278 ret = expect->custom(check, rule, last_read);
2279 goto out;
2280 default:
2281 /* Should never happen. */
2282 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002283 goto out;
2284 }
2285
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002286
Christopher Faulet61cc8522020-04-20 14:54:42 +02002287 /* Wait for more data on mismatch only if no minimum is defined (-1),
2288 * otherwise the absence of match is already conclusive.
2289 */
2290 if (!match && !last_read && (expect->min_recv == -1)) {
2291 ret = TCPCHK_EVAL_WAIT;
2292 goto out;
2293 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002294
Christopher Faulet61cc8522020-04-20 14:54:42 +02002295 /* Result as expected, next rule. */
2296 if (match ^ inverse)
2297 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002298
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002299
Christopher Faulet61cc8522020-04-20 14:54:42 +02002300 /* From this point on, we matched something we did not want, this is an error state. */
2301 ret = TCPCHK_EVAL_STOP;
2302 msg = alloc_trash_chunk();
2303 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002304 tcpcheck_expect_onerror_message(msg, check, rule, match, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002305 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
2306 free_trash_chunk(msg);
2307 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002308
Christopher Faulet61cc8522020-04-20 14:54:42 +02002309 out:
2310 return ret;
2311}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002312
Christopher Faulet61cc8522020-04-20 14:54:42 +02002313/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2314 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It nevers
2315 * waits.
2316 */
2317static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2318{
2319 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2320 struct act_rule *act_rule;
2321 enum act_return act_ret;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002322
Christopher Faulet61cc8522020-04-20 14:54:42 +02002323 act_rule =rule->action_kw.rule;
2324 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2325 if (act_ret != ACT_RET_CONT) {
2326 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2327 tcpcheck_get_step_id(check, rule));
2328 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2329 ret = TCPCHK_EVAL_STOP;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002330 }
2331
Christopher Faulet61cc8522020-04-20 14:54:42 +02002332 return ret;
2333}
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002334
Christopher Faulet61cc8522020-04-20 14:54:42 +02002335/* Executes a tcp-check ruleset. Note that this is called both from the
2336 * connection's wake() callback and from the check scheduling task. It returns
2337 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2338 * presenting the risk of an fd replacement.
2339 *
2340 * Please do NOT place any return statement in this function and only leave
2341 * via the out_end_tcpcheck label after setting retcode.
2342 */
2343static int tcpcheck_main(struct check *check)
2344{
2345 struct tcpcheck_rule *rule;
2346 struct conn_stream *cs = check->cs;
2347 struct connection *conn = cs_conn(cs);
2348 int must_read = 1, last_read = 0;
2349 int ret, retcode = 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002350
Christopher Faulet61cc8522020-04-20 14:54:42 +02002351 /* here, we know that the check is complete or that it failed */
2352 if (check->result != CHK_RES_UNKNOWN)
2353 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002354
Christopher Faulet61cc8522020-04-20 14:54:42 +02002355 /* 1- check for connection error, if any */
2356 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2357 goto out_end_tcpcheck;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002358
Christopher Faulet61cc8522020-04-20 14:54:42 +02002359 /* 2- check if we are waiting for the connection establishment. It only
2360 * happens during TCPCHK_ACT_CONNECT. */
2361 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
2362 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
2363 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
2364 if (rule->action == TCPCHK_ACT_SEND)
2365 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2366 else if (rule->action == TCPCHK_ACT_EXPECT)
2367 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2368 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002369 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002370 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002371
2372 /* 3- check for pending outgoing data. It only happens during
2373 * TCPCHK_ACT_SEND. */
2374 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
2375 if (conn && b_data(&check->bo)) {
Christopher Faulet6d471212020-04-22 11:09:25 +02002376 ret = conn->mux->snd_buf(cs, &check->bo,
2377 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002378 if (ret <= 0) {
2379 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2380 goto out_end_tcpcheck;
Christopher Faulet61cc8522020-04-20 14:54:42 +02002381 }
Christopher Faulet6d471212020-04-22 11:09:25 +02002382 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02002383 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2384 goto out;
2385 }
2386 }
2387 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002388 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002389
Christopher Faulet61cc8522020-04-20 14:54:42 +02002390 /* 4- check if a rule must be resume. It happens if check->current_step
2391 * is defined. */
2392 else if (check->current_step)
2393 rule = check->current_step;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002394
Christopher Faulet61cc8522020-04-20 14:54:42 +02002395 /* 5- It is the first evaluation. We must create a session and preset
2396 * tcp-check variables */
2397 else {
2398 struct tcpcheck_var *var;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002399
Christopher Faulet61cc8522020-04-20 14:54:42 +02002400 /* First evaluation, create a session */
2401 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2402 if (!check->sess) {
2403 chunk_printf(&trash, "TCPCHK error allocating check session");
2404 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2405 goto out_end_tcpcheck;
2406 }
2407 vars_init(&check->vars, SCOPE_CHECK);
2408 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Willy Tarreauef953952014-10-02 14:30:14 +02002409
Christopher Faulet61cc8522020-04-20 14:54:42 +02002410 /* Preset tcp-check variables */
2411 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2412 struct sample smp;
Willy Tarreau449f9522015-05-13 15:39:48 +02002413
Christopher Faulet61cc8522020-04-20 14:54:42 +02002414 memset(&smp, 0, sizeof(smp));
2415 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2416 smp.data = var->data;
Christopher Fauletb61caf42020-04-21 10:57:42 +02002417 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002418 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002419 }
2420
Christopher Faulet61cc8522020-04-20 14:54:42 +02002421 /* Now evaluate the tcp-check rules */
Willy Tarreaudeccd112018-06-14 18:38:55 +02002422
Christopher Faulet61cc8522020-04-20 14:54:42 +02002423 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2424 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002425
Christopher Faulet61cc8522020-04-20 14:54:42 +02002426 check->code = 0;
2427 switch (rule->action) {
2428 case TCPCHK_ACT_CONNECT:
2429 check->current_step = rule;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002430
Christopher Faulet61cc8522020-04-20 14:54:42 +02002431 /* close but not release yet previous connection */
2432 if (check->cs) {
2433 cs_close(check->cs);
2434 retcode = -1; /* do not reuse the fd in the caller! */
2435 }
2436 eval_ret = tcpcheck_eval_connect(check, rule);
2437 must_read = 1; last_read = 0;
2438 break;
2439 case TCPCHK_ACT_SEND:
2440 check->current_step = rule;
2441 eval_ret = tcpcheck_eval_send(check, rule);
2442 must_read = 1;
2443 break;
2444 case TCPCHK_ACT_EXPECT:
2445 check->current_step = rule;
2446 if (must_read) {
2447 if (check->proxy->timeout.check)
2448 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Willy Tarreauf3d34822014-12-08 12:11:28 +01002449
Christopher Faulet61cc8522020-04-20 14:54:42 +02002450 eval_ret = tcpcheck_eval_recv(check, rule);
2451 if (eval_ret == TCPCHK_EVAL_STOP)
2452 goto out_end_tcpcheck;
2453 else if (eval_ret == TCPCHK_EVAL_WAIT)
2454 goto out;
2455 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2456 must_read = 0;
2457 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002458
Christopher Faulet61cc8522020-04-20 14:54:42 +02002459 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2460 ? tcpcheck_eval_expect_http(check, rule, last_read)
2461 : tcpcheck_eval_expect(check, rule, last_read));
Willy Tarreau00149122017-10-04 18:05:01 +02002462
Christopher Faulet61cc8522020-04-20 14:54:42 +02002463 if (eval_ret == TCPCHK_EVAL_WAIT) {
2464 check->current_step = rule->expect.head;
2465 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2466 }
2467 break;
2468 case TCPCHK_ACT_ACTION_KW:
2469 /* Don't update the current step */
2470 eval_ret = tcpcheck_eval_action_kw(check, rule);
2471 break;
2472 default:
2473 /* Otherwise, just go to the next one and don't update
2474 * the current step
2475 */
2476 eval_ret = TCPCHK_EVAL_CONTINUE;
2477 break;
2478 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002479
Christopher Faulet61cc8522020-04-20 14:54:42 +02002480 switch (eval_ret) {
2481 case TCPCHK_EVAL_CONTINUE:
2482 break;
2483 case TCPCHK_EVAL_WAIT:
2484 goto out;
2485 case TCPCHK_EVAL_STOP:
2486 goto out_end_tcpcheck;
2487 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02002488 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002489
Christopher Faulet61cc8522020-04-20 14:54:42 +02002490 /* All rules was evaluated */
2491 if (check->current_step) {
2492 rule = check->current_step;
Willy Tarreau00149122017-10-04 18:05:01 +02002493
Christopher Faulet61cc8522020-04-20 14:54:42 +02002494 if (rule->action == TCPCHK_ACT_EXPECT) {
2495 struct buffer *msg;
Willy Tarreau00149122017-10-04 18:05:01 +02002496
Christopher Faulet61cc8522020-04-20 14:54:42 +02002497 if (check->server &&
2498 (check->server->proxy->options & PR_O_DISABLE404) &&
2499 (check->server->next_state != SRV_ST_STOPPED) &&
2500 (check->code == 404)) {
2501 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2502 goto out_end_tcpcheck;
2503 }
Christopher Fauletd7e63962020-04-17 20:15:59 +02002504
Christopher Faulet61cc8522020-04-20 14:54:42 +02002505 msg = alloc_trash_chunk();
2506 if (msg)
Christopher Fauletb61caf42020-04-21 10:57:42 +02002507 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
Christopher Faulet61cc8522020-04-20 14:54:42 +02002508 set_server_check_status(check, rule->expect.ok_status,
2509 (msg ? b_head(msg) : "(tcp-check)"));
2510 free_trash_chunk(msg);
2511 }
2512 else if (rule->action == TCPCHK_ACT_CONNECT) {
2513 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
Christopher Fauletf73f5cc2020-04-27 12:06:55 +02002514 enum healthcheck_status status = HCHK_STATUS_L4OK;
2515#ifdef USE_OPENSSL
2516 if (conn && ssl_sock_is_ssl(conn))
2517 status = HCHK_STATUS_L6OK;
2518#endif
Christopher Faulet61cc8522020-04-20 14:54:42 +02002519 set_server_check_status(check, status, msg);
2520 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002521 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002522 else
2523 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet14cd3162020-04-16 14:50:06 +02002524
Christopher Faulet61cc8522020-04-20 14:54:42 +02002525 out_end_tcpcheck:
2526 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2527 chk_report_conn_err(check, errno, 0);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002528
Christopher Faulet61cc8522020-04-20 14:54:42 +02002529 /* cleanup before leaving */
2530 check->current_step = NULL;
2531 if (check->sess != NULL) {
2532 vars_prune(&check->vars, check->sess, NULL);
2533 session_free(check->sess);
2534 check->sess = NULL;
2535 }
2536 out:
2537 return retcode;
2538}
Christopher Fauletbb591a12020-04-01 16:52:17 +02002539
Christopher Faulet14cd3162020-04-16 14:50:06 +02002540
Christopher Faulet61cc8522020-04-20 14:54:42 +02002541/**************************************************************************/
2542/************** Health-checks based on an external process ****************/
2543/**************************************************************************/
2544static struct list pid_list = LIST_HEAD_INIT(pid_list);
2545static struct pool_head *pool_head_pid_list;
2546__decl_spinlock(pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002547
Christopher Faulet61cc8522020-04-20 14:54:42 +02002548struct extcheck_env {
2549 char *name; /* environment variable name */
2550 int vmaxlen; /* value maximum length, used to determine the required memory allocation */
2551};
Christopher Faulet14cd3162020-04-16 14:50:06 +02002552
Christopher Faulet61cc8522020-04-20 14:54:42 +02002553/* environment variables memory requirement for different types of data */
2554#define EXTCHK_SIZE_EVAL_INIT 0 /* size determined during the init phase,
2555 * such environment variables are not updatable. */
2556#define EXTCHK_SIZE_ULONG 20 /* max string length for an unsigned long value */
2557#define EXTCHK_SIZE_UINT 11 /* max string length for an unsigned int value */
2558#define EXTCHK_SIZE_ADDR INET6_ADDRSTRLEN+1 /* max string length for an address */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002559
Christopher Faulet61cc8522020-04-20 14:54:42 +02002560/* external checks environment variables */
2561enum {
2562 EXTCHK_PATH = 0,
Willy Tarreauca79f592019-07-17 19:04:47 +02002563
Christopher Faulet61cc8522020-04-20 14:54:42 +02002564 /* Proxy specific environment variables */
2565 EXTCHK_HAPROXY_PROXY_NAME, /* the backend name */
2566 EXTCHK_HAPROXY_PROXY_ID, /* the backend id */
2567 EXTCHK_HAPROXY_PROXY_ADDR, /* the first bind address if available (or empty) */
2568 EXTCHK_HAPROXY_PROXY_PORT, /* the first bind port if available (or empty) */
Christopher Fauletbb591a12020-04-01 16:52:17 +02002569
Christopher Faulet61cc8522020-04-20 14:54:42 +02002570 /* Server specific environment variables */
2571 EXTCHK_HAPROXY_SERVER_NAME, /* the server name */
2572 EXTCHK_HAPROXY_SERVER_ID, /* the server id */
2573 EXTCHK_HAPROXY_SERVER_ADDR, /* the server address */
2574 EXTCHK_HAPROXY_SERVER_PORT, /* the server port if available (or empty) */
2575 EXTCHK_HAPROXY_SERVER_MAXCONN, /* the server max connections */
2576 EXTCHK_HAPROXY_SERVER_CURCONN, /* the current number of connections on the server */
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002577
Christopher Faulet61cc8522020-04-20 14:54:42 +02002578 EXTCHK_SIZE
2579};
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002580
Christopher Faulet61cc8522020-04-20 14:54:42 +02002581const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
2582 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
2583 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
2584 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
2585 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
2586 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
2587 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
2588 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
2589 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
2590 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
2591 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
2592 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
2593};
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002594
Christopher Faulet61cc8522020-04-20 14:54:42 +02002595void block_sigchld(void)
2596{
2597 sigset_t set;
2598 sigemptyset(&set);
2599 sigaddset(&set, SIGCHLD);
2600 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
2601}
Willy Tarreaube373152018-09-06 11:45:30 +02002602
Christopher Faulet61cc8522020-04-20 14:54:42 +02002603void unblock_sigchld(void)
2604{
2605 sigset_t set;
2606 sigemptyset(&set);
2607 sigaddset(&set, SIGCHLD);
2608 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002609}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002610
Christopher Faulet61cc8522020-04-20 14:54:42 +02002611static struct pid_list *pid_list_add(pid_t pid, struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002612{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002613 struct pid_list *elem;
2614 struct check *check = t->context;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002615
Christopher Faulet61cc8522020-04-20 14:54:42 +02002616 elem = pool_alloc(pool_head_pid_list);
2617 if (!elem)
2618 return NULL;
2619 elem->pid = pid;
2620 elem->t = t;
2621 elem->exited = 0;
2622 check->curpid = elem;
2623 LIST_INIT(&elem->list);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002624
Christopher Faulet61cc8522020-04-20 14:54:42 +02002625 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2626 LIST_ADD(&pid_list, &elem->list);
2627 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002628
Christopher Faulet61cc8522020-04-20 14:54:42 +02002629 return elem;
2630}
Christopher Faulete5870d82020-04-15 11:32:03 +02002631
Christopher Faulet61cc8522020-04-20 14:54:42 +02002632static void pid_list_del(struct pid_list *elem)
2633{
2634 struct check *check;
Christopher Faulete5870d82020-04-15 11:32:03 +02002635
Christopher Faulet61cc8522020-04-20 14:54:42 +02002636 if (!elem)
2637 return;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002638
Christopher Faulet61cc8522020-04-20 14:54:42 +02002639 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2640 LIST_DEL(&elem->list);
2641 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002642
Christopher Faulet61cc8522020-04-20 14:54:42 +02002643 if (!elem->exited)
2644 kill(elem->pid, SIGTERM);
Christopher Faulet14cd3162020-04-16 14:50:06 +02002645
Christopher Faulet61cc8522020-04-20 14:54:42 +02002646 check = elem->t->context;
2647 check->curpid = NULL;
2648 pool_free(pool_head_pid_list, elem);
2649}
Christopher Faulet14cd3162020-04-16 14:50:06 +02002650
Christopher Faulet61cc8522020-04-20 14:54:42 +02002651/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
2652static void pid_list_expire(pid_t pid, int status)
2653{
2654 struct pid_list *elem;
Christopher Faulete5870d82020-04-15 11:32:03 +02002655
Christopher Faulet61cc8522020-04-20 14:54:42 +02002656 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
2657 list_for_each_entry(elem, &pid_list, list) {
2658 if (elem->pid == pid) {
2659 elem->t->expire = now_ms;
2660 elem->status = status;
2661 elem->exited = 1;
2662 task_wakeup(elem->t, TASK_WOKEN_IO);
2663 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002664 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002665 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002666 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
2667}
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002668
Christopher Faulet61cc8522020-04-20 14:54:42 +02002669static void sigchld_handler(struct sig_handler *sh)
2670{
2671 pid_t pid;
2672 int status;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002673
Christopher Faulet61cc8522020-04-20 14:54:42 +02002674 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
2675 pid_list_expire(pid, status);
2676}
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002677
Christopher Faulet61cc8522020-04-20 14:54:42 +02002678static int init_pid_list(void)
2679{
2680 if (pool_head_pid_list != NULL)
2681 /* Nothing to do */
2682 return 0;
2683
2684 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
2685 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
2686 strerror(errno));
2687 return 1;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002688 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002689
Christopher Faulet61cc8522020-04-20 14:54:42 +02002690 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
2691 if (pool_head_pid_list == NULL) {
2692 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
2693 strerror(errno));
2694 return 1;
2695 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002696
Christopher Faulet61cc8522020-04-20 14:54:42 +02002697 return 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02002698}
2699
Christopher Faulet61cc8522020-04-20 14:54:42 +02002700/* helper macro to set an environment variable and jump to a specific label on failure. */
2701#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002702
Christopher Faulet61cc8522020-04-20 14:54:42 +02002703/*
2704 * helper function to allocate enough memory to store an environment variable.
2705 * It will also check that the environment variable is updatable, and silently
2706 * fail if not.
2707 */
2708static int extchk_setenv(struct check *check, int idx, const char *value)
2709{
2710 int len, ret;
2711 char *envname;
2712 int vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002713
Christopher Faulet61cc8522020-04-20 14:54:42 +02002714 if (idx < 0 || idx >= EXTCHK_SIZE) {
2715 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
2716 return 1;
2717 }
Christopher Fauletf9585d82020-04-16 13:25:58 +02002718
Christopher Faulet61cc8522020-04-20 14:54:42 +02002719 envname = extcheck_envs[idx].name;
2720 vmaxlen = extcheck_envs[idx].vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002721
Christopher Faulet61cc8522020-04-20 14:54:42 +02002722 /* Check if the environment variable is already set, and silently reject
2723 * the update if this one is not updatable. */
2724 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
2725 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002726
Christopher Faulet61cc8522020-04-20 14:54:42 +02002727 /* Instead of sending NOT_USED, sending an empty value is preferable */
2728 if (strcmp(value, "NOT_USED") == 0) {
2729 value = "";
Christopher Fauletf9585d82020-04-16 13:25:58 +02002730 }
2731
Christopher Faulet61cc8522020-04-20 14:54:42 +02002732 len = strlen(envname) + 1;
2733 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
2734 len += strlen(value);
2735 else
2736 len += vmaxlen;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002737
Christopher Faulet61cc8522020-04-20 14:54:42 +02002738 if (!check->envp[idx])
2739 check->envp[idx] = malloc(len + 1);
Christopher Fauletf9585d82020-04-16 13:25:58 +02002740
Christopher Faulet61cc8522020-04-20 14:54:42 +02002741 if (!check->envp[idx]) {
2742 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
2743 return 1;
2744 }
2745 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
2746 if (ret < 0) {
2747 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
2748 return 1;
2749 }
2750 else if (ret > len) {
2751 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
2752 return 1;
2753 }
2754 return 0;
Christopher Fauletf9585d82020-04-16 13:25:58 +02002755}
2756
Christopher Faulet61cc8522020-04-20 14:54:42 +02002757static int prepare_external_check(struct check *check)
Christopher Faulete5870d82020-04-15 11:32:03 +02002758{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002759 struct server *s = check->server;
2760 struct proxy *px = s->proxy;
2761 struct listener *listener = NULL, *l;
2762 int i;
2763 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
2764 char buf[256];
Christopher Faulete5870d82020-04-15 11:32:03 +02002765
Christopher Faulet61cc8522020-04-20 14:54:42 +02002766 list_for_each_entry(l, &px->conf.listeners, by_fe)
2767 /* Use the first INET, INET6 or UNIX listener */
2768 if (l->addr.ss_family == AF_INET ||
2769 l->addr.ss_family == AF_INET6 ||
2770 l->addr.ss_family == AF_UNIX) {
2771 listener = l;
2772 break;
2773 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002774
Christopher Faulet61cc8522020-04-20 14:54:42 +02002775 check->curpid = NULL;
2776 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
2777 if (!check->envp) {
2778 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
2779 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002780 }
2781
Christopher Faulet61cc8522020-04-20 14:54:42 +02002782 check->argv = calloc(6, sizeof(char *));
2783 if (!check->argv) {
2784 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2785 goto err;
Christopher Faulet14cd3162020-04-16 14:50:06 +02002786 }
2787
Christopher Faulet61cc8522020-04-20 14:54:42 +02002788 check->argv[0] = px->check_command;
Christopher Faulete5870d82020-04-15 11:32:03 +02002789
Christopher Faulet61cc8522020-04-20 14:54:42 +02002790 if (!listener) {
2791 check->argv[1] = strdup("NOT_USED");
2792 check->argv[2] = strdup("NOT_USED");
Christopher Faulete5870d82020-04-15 11:32:03 +02002793 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002794 else if (listener->addr.ss_family == AF_INET ||
2795 listener->addr.ss_family == AF_INET6) {
2796 addr_to_str(&listener->addr, buf, sizeof(buf));
2797 check->argv[1] = strdup(buf);
2798 port_to_str(&listener->addr, buf, sizeof(buf));
2799 check->argv[2] = strdup(buf);
2800 }
2801 else if (listener->addr.ss_family == AF_UNIX) {
2802 const struct sockaddr_un *un;
Christopher Faulete5870d82020-04-15 11:32:03 +02002803
Christopher Faulet61cc8522020-04-20 14:54:42 +02002804 un = (struct sockaddr_un *)&listener->addr;
2805 check->argv[1] = strdup(un->sun_path);
2806 check->argv[2] = strdup("NOT_USED");
2807 }
2808 else {
2809 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
2810 goto err;
2811 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002812
Christopher Faulet61cc8522020-04-20 14:54:42 +02002813 if (!check->argv[1] || !check->argv[2]) {
2814 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2815 goto err;
2816 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002817
Christopher Faulet61cc8522020-04-20 14:54:42 +02002818 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
2819 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
2820 if (!check->argv[3] || !check->argv[4]) {
2821 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2822 goto err;
2823 }
Christopher Faulet14cd3162020-04-16 14:50:06 +02002824
Christopher Faulet61cc8522020-04-20 14:54:42 +02002825 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2826 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2827 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Christopher Faulete5870d82020-04-15 11:32:03 +02002828
Christopher Faulet61cc8522020-04-20 14:54:42 +02002829 for (i = 0; i < 5; i++) {
2830 if (!check->argv[i]) {
2831 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
2832 goto err;
Christopher Faulete5870d82020-04-15 11:32:03 +02002833 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002834 }
Christopher Faulete5870d82020-04-15 11:32:03 +02002835
Christopher Faulet61cc8522020-04-20 14:54:42 +02002836 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
2837 /* Add proxy environment variables */
2838 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
2839 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
2840 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
2841 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
2842 /* Add server environment variables */
2843 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
2844 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
2845 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
2846 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
2847 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
2848 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002849
Christopher Faulet61cc8522020-04-20 14:54:42 +02002850 /* Ensure that we don't leave any hole in check->envp */
2851 for (i = 0; i < EXTCHK_SIZE; i++)
2852 if (!check->envp[i])
2853 EXTCHK_SETENV(check, i, "", err);
Christopher Faulete5870d82020-04-15 11:32:03 +02002854
Christopher Faulet61cc8522020-04-20 14:54:42 +02002855 return 1;
2856err:
2857 if (check->envp) {
2858 for (i = 0; i < EXTCHK_SIZE; i++)
2859 free(check->envp[i]);
2860 free(check->envp);
2861 check->envp = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002862 }
2863
Christopher Faulet61cc8522020-04-20 14:54:42 +02002864 if (check->argv) {
2865 for (i = 1; i < 5; i++)
2866 free(check->argv[i]);
2867 free(check->argv);
2868 check->argv = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02002869 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002870 return 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002871}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01002872
Christopher Faulet61cc8522020-04-20 14:54:42 +02002873/*
2874 * establish a server health-check that makes use of a process.
2875 *
2876 * It can return one of :
2877 * - SF_ERR_NONE if everything's OK
2878 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2879 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2880 *
2881 * Blocks and then unblocks SIGCHLD
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002882 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002883static int connect_proc_chk(struct task *t)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002884{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002885 char buf[256];
2886 struct check *check = t->context;
2887 struct server *s = check->server;
2888 struct proxy *px = s->proxy;
2889 int status;
2890 pid_t pid;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002891
Christopher Faulet61cc8522020-04-20 14:54:42 +02002892 status = SF_ERR_RESOURCE;
Willy Tarreauf2c87352015-05-13 12:08:21 +02002893
Christopher Faulet61cc8522020-04-20 14:54:42 +02002894 block_sigchld();
Willy Tarreau7df8ca62019-07-15 10:57:51 +02002895
Christopher Faulet61cc8522020-04-20 14:54:42 +02002896 pid = fork();
2897 if (pid < 0) {
2898 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
2899 (global.tune.options & GTUNE_INSECURE_FORK) ?
2900 "" : " (likely caused by missing 'insecure-fork-wanted')",
2901 strerror(errno));
2902 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002903 goto out;
2904 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02002905 if (pid == 0) {
2906 /* Child */
2907 extern char **environ;
2908 struct rlimit limit;
2909 int fd;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01002910
Christopher Faulet61cc8522020-04-20 14:54:42 +02002911 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
2912 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002913
Christopher Faulet61cc8522020-04-20 14:54:42 +02002914 my_closefrom(fd);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002915
Christopher Faulet61cc8522020-04-20 14:54:42 +02002916 /* restore the initial FD limits */
2917 limit.rlim_cur = rlim_fd_cur_at_boot;
2918 limit.rlim_max = rlim_fd_max_at_boot;
2919 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
2920 getrlimit(RLIMIT_NOFILE, &limit);
2921 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
2922 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
2923 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
2924 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002925
Christopher Faulet61cc8522020-04-20 14:54:42 +02002926 environ = check->envp;
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002927
Christopher Faulet61cc8522020-04-20 14:54:42 +02002928 /* Update some environment variables and command args: curconn, server addr and server port */
2929 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002930
Christopher Faulet61cc8522020-04-20 14:54:42 +02002931 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
2932 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002933
Christopher Faulet61cc8522020-04-20 14:54:42 +02002934 *check->argv[4] = 0;
2935 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
2936 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
2937 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002938
Christopher Faulet61cc8522020-04-20 14:54:42 +02002939 haproxy_unblock_signals();
2940 execvp(px->check_command, check->argv);
2941 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
2942 strerror(errno));
2943 exit(-1);
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002944 }
2945
Christopher Faulet61cc8522020-04-20 14:54:42 +02002946 /* Parent */
2947 if (check->result == CHK_RES_UNKNOWN) {
2948 if (pid_list_add(pid, t) != NULL) {
2949 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
2950
2951 if (px->timeout.check && px->timeout.connect) {
2952 int t_con = tick_add(now_ms, px->timeout.connect);
2953 t->expire = tick_first(t->expire, t_con);
2954 }
2955 status = SF_ERR_NONE;
2956 goto out;
2957 }
2958 else {
2959 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2960 }
2961 kill(pid, SIGTERM); /* process creation error */
2962 }
2963 else
2964 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
2965
2966out:
2967 unblock_sigchld();
2968 return status;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01002969}
2970
Christopher Faulet61cc8522020-04-20 14:54:42 +02002971/*
2972 * manages a server health-check that uses an external process. Returns
2973 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002974 *
2975 * Please do NOT place any return statement in this function and only leave
Christopher Faulet61cc8522020-04-20 14:54:42 +02002976 * via the out_unlock label.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002977 */
Christopher Faulet61cc8522020-04-20 14:54:42 +02002978static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002979{
Christopher Faulet61cc8522020-04-20 14:54:42 +02002980 struct check *check = context;
2981 struct server *s = check->server;
2982 int rv;
2983 int ret;
2984 int expired = tick_is_expired(t->expire, now_ms);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002985
Christopher Faulet61cc8522020-04-20 14:54:42 +02002986 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
2987 if (!(check->state & CHK_ST_INPROGRESS)) {
2988 /* no check currently running */
2989 if (!expired) /* woke up too early */
2990 goto out_unlock;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002991
Christopher Faulet61cc8522020-04-20 14:54:42 +02002992 /* we don't send any health-checks when the proxy is
2993 * stopped, the server should not be checked or the check
2994 * is disabled.
2995 */
2996 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
2997 s->proxy->state == PR_STSTOPPED)
2998 goto reschedule;
Gaetan Rivetb616add2020-02-07 15:37:17 +01002999
Christopher Faulet61cc8522020-04-20 14:54:42 +02003000 /* we'll initiate a new check */
3001 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003002
Christopher Faulet61cc8522020-04-20 14:54:42 +02003003 check->state |= CHK_ST_INPROGRESS;
3004
3005 ret = connect_proc_chk(t);
3006 if (ret == SF_ERR_NONE) {
3007 /* the process was forked, we allow up to min(inter,
3008 * timeout.connect) for it to report its status, but
3009 * only when timeout.check is set as it may be to short
3010 * for a full check otherwise.
3011 */
3012 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
3013
3014 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
3015 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
3016 t->expire = tick_first(t->expire, t_con);
Christopher Faulet370e0f12020-04-16 09:52:42 +02003017 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003018 task_set_affinity(t, tid_bit);
3019 goto reschedule;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003020 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003021
Christopher Faulet61cc8522020-04-20 14:54:42 +02003022 /* here, we failed to start the check */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003023
Christopher Faulet61cc8522020-04-20 14:54:42 +02003024 check->state &= ~CHK_ST_INPROGRESS;
3025 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003026
Christopher Faulet61cc8522020-04-20 14:54:42 +02003027 /* we allow up to min(inter, timeout.connect) for a connection
3028 * to establish but only when timeout.check is set
3029 * as it may be to short for a full check otherwise
3030 */
3031 while (tick_is_expired(t->expire, now_ms)) {
3032 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003033
Christopher Faulet61cc8522020-04-20 14:54:42 +02003034 t_con = tick_add(t->expire, s->proxy->timeout.connect);
3035 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003036
Christopher Faulet61cc8522020-04-20 14:54:42 +02003037 if (s->proxy->timeout.check)
3038 t->expire = tick_first(t->expire, t_con);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003039 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003040 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003041 else {
3042 /* there was a test running.
3043 * First, let's check whether there was an uncaught error,
3044 * which can happen on connect timeout or error.
3045 */
3046 if (check->result == CHK_RES_UNKNOWN) {
3047 /* good connection is enough for pure TCP check */
3048 struct pid_list *elem = check->curpid;
3049 int status = HCHK_STATUS_UNKNOWN;
Christopher Faulet370e0f12020-04-16 09:52:42 +02003050
Christopher Faulet61cc8522020-04-20 14:54:42 +02003051 if (elem->exited) {
3052 status = elem->status; /* Save in case the process exits between use below */
3053 if (!WIFEXITED(status))
3054 check->code = -1;
3055 else
3056 check->code = WEXITSTATUS(status);
3057 if (!WIFEXITED(status) || WEXITSTATUS(status))
3058 status = HCHK_STATUS_PROCERR;
3059 else
3060 status = HCHK_STATUS_PROCOK;
3061 } else if (expired) {
3062 status = HCHK_STATUS_PROCTOUT;
3063 ha_warning("kill %d\n", (int)elem->pid);
3064 kill(elem->pid, SIGTERM);
3065 }
3066 set_server_check_status(check, status, NULL);
3067 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003068
Christopher Faulet61cc8522020-04-20 14:54:42 +02003069 if (check->result == CHK_RES_FAILED) {
3070 /* a failure or timeout detected */
3071 check_notify_failure(check);
3072 }
3073 else if (check->result == CHK_RES_CONDPASS) {
3074 /* check is OK but asks for stopping mode */
3075 check_notify_stopping(check);
3076 }
3077 else if (check->result == CHK_RES_PASSED) {
3078 /* a success was detected */
3079 check_notify_success(check);
3080 }
3081 task_set_affinity(t, 1);
3082 check->state &= ~CHK_ST_INPROGRESS;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003083
Christopher Faulet61cc8522020-04-20 14:54:42 +02003084 pid_list_del(check->curpid);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003085
Christopher Faulet61cc8522020-04-20 14:54:42 +02003086 rv = 0;
3087 if (global.spread_checks > 0) {
3088 rv = srv_getinter(check) * global.spread_checks / 100;
3089 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3090 }
3091 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
3092 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003093
Christopher Faulet61cc8522020-04-20 14:54:42 +02003094 reschedule:
3095 while (tick_is_expired(t->expire, now_ms))
3096 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Christopher Faulete5870d82020-04-15 11:32:03 +02003097
Christopher Faulet61cc8522020-04-20 14:54:42 +02003098 out_unlock:
3099 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3100 return t;
3101}
Baptiste Assmann248f1172018-03-01 21:49:01 +01003102
Baptiste Assmann248f1172018-03-01 21:49:01 +01003103
Christopher Faulet61cc8522020-04-20 14:54:42 +02003104/**************************************************************************/
3105/***************** Health-checks based on connections *********************/
3106/**************************************************************************/
3107/* This function is used only for server health-checks. It handles connection
3108 * status updates including errors. If necessary, it wakes the check task up.
3109 * It returns 0 on normal cases, <0 if at least one close() has happened on the
3110 * connection (eg: reconnect). It relies on tcpcheck_main().
3111 */
3112static int wake_srv_chk(struct conn_stream *cs)
3113{
3114 struct connection *conn = cs->conn;
3115 struct check *check = cs->data;
3116 struct email_alertq *q = container_of(check, typeof(*q), check);
3117 int ret = 0;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003118
Christopher Faulet61cc8522020-04-20 14:54:42 +02003119 if (check->server)
3120 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3121 else
3122 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulete5870d82020-04-15 11:32:03 +02003123
Christopher Faulet61cc8522020-04-20 14:54:42 +02003124 /* we may have to make progress on the TCP checks */
3125 ret = tcpcheck_main(check);
Christopher Fauletdf38f882020-04-07 16:04:38 +02003126
Christopher Faulet61cc8522020-04-20 14:54:42 +02003127 cs = check->cs;
3128 conn = cs->conn;
Christopher Fauletdf38f882020-04-07 16:04:38 +02003129
Christopher Faulet61cc8522020-04-20 14:54:42 +02003130 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
3131 /* We may get error reports bypassing the I/O handlers, typically
3132 * the case when sending a pure TCP check which fails, then the I/O
3133 * handlers above are not called. This is completely handled by the
3134 * main processing task so let's simply wake it up. If we get here,
3135 * we expect errno to still be valid.
3136 */
3137 chk_report_conn_err(check, errno, 0);
3138 task_wakeup(check->task, TASK_WOKEN_IO);
3139 }
3140
3141 if (check->result != CHK_RES_UNKNOWN) {
3142 /* Check complete or aborted. If connection not yet closed do it
3143 * now and wake the check task up to be sure the result is
3144 * handled ASAP. */
3145 conn_sock_drain(conn);
3146 cs_close(cs);
3147 ret = -1;
3148 /* We may have been scheduled to run, and the
3149 * I/O handler expects to have a cs, so remove
3150 * the tasklet
3151 */
3152 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3153 task_wakeup(check->task, TASK_WOKEN_IO);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003154 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003155
3156 if (check->server)
3157 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Christopher Fauletec07e382020-04-07 14:56:26 +02003158 else
Christopher Faulet61cc8522020-04-20 14:54:42 +02003159 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003160
Christopher Faulet61cc8522020-04-20 14:54:42 +02003161 /* if a connection got replaced, we must absolutely prevent the connection
3162 * handler from touching its fd, and perform the FD polling updates ourselves
3163 */
3164 if (ret < 0)
3165 conn_cond_update_polling(conn);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003166
Christopher Faulet61cc8522020-04-20 14:54:42 +02003167 return ret;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003168}
3169
Christopher Faulet61cc8522020-04-20 14:54:42 +02003170/* This function checks if any I/O is wanted, and if so, attempts to do so */
3171static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Simon Hormanb1900d52015-01-30 11:22:54 +09003172{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003173 struct check *check = ctx;
3174 struct conn_stream *cs = check->cs;
3175 struct email_alertq *q = container_of(check, typeof(*q), check);
3176 int ret = 0;
Simon Hormanb1900d52015-01-30 11:22:54 +09003177
Christopher Faulet61cc8522020-04-20 14:54:42 +02003178 if (!(check->wait_list.events & SUB_RETRY_SEND))
3179 ret = wake_srv_chk(cs);
3180 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
3181 if (check->server)
3182 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3183 else
3184 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Hormanb1900d52015-01-30 11:22:54 +09003185
Christopher Faulet61cc8522020-04-20 14:54:42 +02003186 if (unlikely(check->result == CHK_RES_FAILED)) {
3187 /* collect possible new errors */
3188 if (cs->conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
3189 chk_report_conn_err(check, 0, 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003190
Christopher Faulet61cc8522020-04-20 14:54:42 +02003191 /* Reset the check buffer... */
3192 b_reset(&check->bi);
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003193
Christopher Faulet61cc8522020-04-20 14:54:42 +02003194 /* Close the connection... We still attempt to nicely close if,
3195 * for instance, SSL needs to send a "close notify." Later, we perform
3196 * a hard close and reset the connection if some data are pending,
3197 * otherwise we end up with many TIME_WAITs and eat all the source port
3198 * range quickly. To avoid sending RSTs all the time, we first try to
3199 * drain pending data.
3200 */
3201 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
3202 * connection, to make sure cs_shutw() will not lead to a shutdown()
3203 * that would provoke TIME_WAITs.
3204 */
3205 cs_shutr(cs, CS_SHR_DRAIN);
3206 cs_shutw(cs, CS_SHW_NORMAL);
Simon Hormanb1900d52015-01-30 11:22:54 +09003207
Christopher Faulet61cc8522020-04-20 14:54:42 +02003208 /* OK, let's not stay here forever */
3209 if (check->result == CHK_RES_FAILED)
3210 cs->conn->flags |= CO_FL_ERROR;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003211
Christopher Faulet61cc8522020-04-20 14:54:42 +02003212 task_wakeup(t, TASK_WOKEN_IO);
3213 }
3214
3215 if (check->server)
3216 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3217 else
3218 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003219 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003220 return NULL;
Simon Hormanbfb5d332015-01-30 11:22:55 +09003221}
3222
Christopher Faulet61cc8522020-04-20 14:54:42 +02003223/* manages a server health-check that uses a connection. Returns
3224 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
3225 *
3226 * Please do NOT place any return statement in this function and only leave
3227 * via the out_unlock label.
3228 */
3229static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003230{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003231 struct check *check = context;
3232 struct proxy *proxy = check->proxy;
3233 struct conn_stream *cs = check->cs;
3234 struct connection *conn = cs_conn(cs);
3235 int rv;
3236 int expired = tick_is_expired(t->expire, now_ms);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003237
Christopher Faulet61cc8522020-04-20 14:54:42 +02003238 if (check->server)
3239 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
3240 if (!(check->state & CHK_ST_INPROGRESS)) {
3241 /* no check currently running */
3242 if (!expired) /* woke up too early */
3243 goto out_unlock;
Christopher Faulete5870d82020-04-15 11:32:03 +02003244
Christopher Faulet61cc8522020-04-20 14:54:42 +02003245 /* we don't send any health-checks when the proxy is
3246 * stopped, the server should not be checked or the check
3247 * is disabled.
3248 */
3249 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
3250 proxy->state == PR_STSTOPPED)
3251 goto reschedule;
Christopher Faulete5870d82020-04-15 11:32:03 +02003252
Christopher Faulet61cc8522020-04-20 14:54:42 +02003253 /* we'll initiate a new check */
3254 set_server_check_status(check, HCHK_STATUS_START, NULL);
Christopher Faulete5870d82020-04-15 11:32:03 +02003255
Christopher Faulet61cc8522020-04-20 14:54:42 +02003256 check->state |= CHK_ST_INPROGRESS;
3257 b_reset(&check->bi);
3258 b_reset(&check->bo);
Christopher Faulete5870d82020-04-15 11:32:03 +02003259
Christopher Faulet61cc8522020-04-20 14:54:42 +02003260 task_set_affinity(t, tid_bit);
3261 cs = check->cs;
3262 conn = cs_conn(cs);
3263 if (!conn) {
3264 check->current_step = NULL;
3265 tcpcheck_main(check);
3266 goto out_unlock;
3267 }
Christopher Faulete5870d82020-04-15 11:32:03 +02003268
Christopher Faulet61cc8522020-04-20 14:54:42 +02003269 conn->flags |= CO_FL_ERROR;
3270 chk_report_conn_err(check, 0, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003271
Christopher Faulet61cc8522020-04-20 14:54:42 +02003272 /* here, we have seen a synchronous error, no fd was allocated */
3273 task_set_affinity(t, MAX_THREADS_MASK);
3274 if (cs) {
3275 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003276 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003277 /* We may have been scheduled to run, and the
3278 * I/O handler expects to have a cs, so remove
3279 * the tasklet
3280 */
3281 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3282 cs_destroy(cs);
3283 cs = check->cs = NULL;
3284 conn = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003285 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003286
Christopher Faulet61cc8522020-04-20 14:54:42 +02003287 check->state &= ~CHK_ST_INPROGRESS;
3288 check_notify_failure(check);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003289
Christopher Faulet61cc8522020-04-20 14:54:42 +02003290 /* we allow up to min(inter, timeout.connect) for a connection
3291 * to establish but only when timeout.check is set
3292 * as it may be to short for a full check otherwise
3293 */
3294 while (tick_is_expired(t->expire, now_ms)) {
3295 int t_con;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003296
Christopher Faulet61cc8522020-04-20 14:54:42 +02003297 t_con = tick_add(t->expire, proxy->timeout.connect);
3298 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3299 if (proxy->timeout.check)
3300 t->expire = tick_first(t->expire, t_con);
3301 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003302 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003303 else {
3304 /* there was a test running.
3305 * First, let's check whether there was an uncaught error,
3306 * which can happen on connect timeout or error.
3307 */
3308 if (check->result == CHK_RES_UNKNOWN) {
3309 if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
3310 chk_report_conn_err(check, 0, expired);
3311 }
3312 else
3313 goto out_unlock; /* timeout not reached, wait again */
3314 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003315
Christopher Faulet61cc8522020-04-20 14:54:42 +02003316 /* check complete or aborted */
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003317
Christopher Faulet61cc8522020-04-20 14:54:42 +02003318 check->current_step = NULL;
3319 if (check->sess != NULL) {
3320 session_free(check->sess);
3321 check->sess = NULL;
3322 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003323
Christopher Faulet61cc8522020-04-20 14:54:42 +02003324 if (conn && conn->xprt) {
3325 /* The check was aborted and the connection was not yet closed.
3326 * This can happen upon timeout, or when an external event such
3327 * as a failed response coupled with "observe layer7" caused the
3328 * server state to be suddenly changed.
3329 */
3330 conn_sock_drain(conn);
3331 cs_close(cs);
3332 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003333
Christopher Faulet61cc8522020-04-20 14:54:42 +02003334 if (cs) {
3335 if (check->wait_list.events)
Christopher Faulet0b9376a2020-04-24 16:20:49 +02003336 cs->conn->mux->unsubscribe(cs, check->wait_list.events, &check->wait_list);
Christopher Faulet61cc8522020-04-20 14:54:42 +02003337 /* We may have been scheduled to run, and the
3338 * I/O handler expects to have a cs, so remove
3339 * the tasklet
3340 */
3341 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
3342 cs_destroy(cs);
3343 cs = check->cs = NULL;
3344 conn = NULL;
3345 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003346
Christopher Faulet61cc8522020-04-20 14:54:42 +02003347 if (check->server) {
3348 if (check->result == CHK_RES_FAILED) {
3349 /* a failure or timeout detected */
3350 check_notify_failure(check);
3351 }
3352 else if (check->result == CHK_RES_CONDPASS) {
3353 /* check is OK but asks for stopping mode */
3354 check_notify_stopping(check);
3355 }
3356 else if (check->result == CHK_RES_PASSED) {
3357 /* a success was detected */
3358 check_notify_success(check);
3359 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003360 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003361 task_set_affinity(t, MAX_THREADS_MASK);
3362 check->state &= ~CHK_ST_INPROGRESS;
3363
3364 if (check->server) {
3365 rv = 0;
3366 if (global.spread_checks > 0) {
3367 rv = srv_getinter(check) * global.spread_checks / 100;
3368 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
3369 }
3370 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003371 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003372 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003373
Christopher Faulet61cc8522020-04-20 14:54:42 +02003374 reschedule:
3375 while (tick_is_expired(t->expire, now_ms))
3376 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
3377 out_unlock:
3378 if (check->server)
3379 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
3380 return t;
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003381}
3382
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003383
Christopher Faulet61cc8522020-04-20 14:54:42 +02003384/**************************************************************************/
3385/******************* Internals to parse tcp-check rules *******************/
3386/**************************************************************************/
3387struct action_kw_list tcp_check_keywords = {
3388 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
3389};
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003390
Christopher Faulet61cc8522020-04-20 14:54:42 +02003391/* Return the struct action_kw associated to a keyword */
3392static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003393{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003394 return action_lookup(&tcp_check_keywords.list, kw);
3395}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003396
Christopher Faulet61cc8522020-04-20 14:54:42 +02003397static void action_kw_tcp_check_build_list(struct buffer *chk)
3398{
3399 action_build_list(&tcp_check_keywords.list, chk);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003400}
3401
Christopher Faulet61cc8522020-04-20 14:54:42 +02003402/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
3403 * returned on error.
3404 */
3405static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
3406 struct list *rules, struct action_kw *kw,
3407 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003408{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003409 struct tcpcheck_rule *chk = NULL;
3410 struct act_rule *actrule = NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003411
Christopher Faulet61cc8522020-04-20 14:54:42 +02003412 actrule = calloc(1, sizeof(*actrule));
3413 if (!actrule) {
3414 memprintf(errmsg, "out of memory");
3415 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003416 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003417 actrule->kw = kw;
3418 actrule->from = ACT_F_TCP_CHK;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003419
Christopher Faulet61cc8522020-04-20 14:54:42 +02003420 cur_arg++;
3421 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
3422 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
3423 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003424 }
3425
Christopher Faulet61cc8522020-04-20 14:54:42 +02003426 chk = calloc(1, sizeof(*chk));
3427 if (!chk) {
3428 memprintf(errmsg, "out of memory");
3429 goto error;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003430 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003431 chk->action = TCPCHK_ACT_ACTION_KW;
3432 chk->action_kw.rule = actrule;
3433 return chk;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003434
3435 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02003436 free(actrule);
3437 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003438}
3439
Christopher Faulet61cc8522020-04-20 14:54:42 +02003440/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
3441 * returned on error.
3442 */
3443static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
3444 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003445{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003446 struct tcpcheck_rule *chk = NULL;
3447 struct sockaddr_storage *sk = NULL;
3448 char *comment = NULL, *sni = NULL, *alpn = NULL;
3449 struct sample_expr *port_expr = NULL;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003450 const struct mux_proto_list *mux_proto = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003451 unsigned short conn_opts = 0;
3452 long port = 0;
3453 int alpn_len = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003454
Christopher Faulet61cc8522020-04-20 14:54:42 +02003455 list_for_each_entry(chk, rules, list) {
3456 if (chk->action == TCPCHK_ACT_CONNECT)
3457 break;
3458 if (chk->action == TCPCHK_ACT_COMMENT ||
3459 chk->action == TCPCHK_ACT_ACTION_KW ||
3460 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3461 continue;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003462
Christopher Faulet61cc8522020-04-20 14:54:42 +02003463 memprintf(errmsg, "first step MUST also be a 'connect', "
3464 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
3465 "when there is a 'connect' step in the tcp-check ruleset");
3466 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003467 }
3468
Christopher Faulet61cc8522020-04-20 14:54:42 +02003469 cur_arg++;
3470 while (*(args[cur_arg])) {
3471 if (strcmp(args[cur_arg], "default") == 0)
3472 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
3473 else if (strcmp(args[cur_arg], "addr") == 0) {
3474 int port1, port2;
3475 struct protocol *proto;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003476
Christopher Faulet61cc8522020-04-20 14:54:42 +02003477 if (!*(args[cur_arg+1])) {
3478 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
3479 goto error;
3480 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003481
Christopher Faulet61cc8522020-04-20 14:54:42 +02003482 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
3483 if (!sk) {
3484 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
3485 goto error;
3486 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003487
Christopher Faulet61cc8522020-04-20 14:54:42 +02003488 proto = protocol_by_family(sk->ss_family);
3489 if (!proto || !proto->connect) {
3490 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
3491 args[cur_arg]);
3492 goto error;
3493 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003494
Christopher Faulet61cc8522020-04-20 14:54:42 +02003495 if (port1 != port2) {
3496 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
3497 args[cur_arg], args[cur_arg+1]);
3498 goto error;
3499 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003500
Christopher Faulet61cc8522020-04-20 14:54:42 +02003501 cur_arg++;
3502 }
3503 else if (strcmp(args[cur_arg], "port") == 0) {
3504 const char *p, *end;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003505
Christopher Faulet61cc8522020-04-20 14:54:42 +02003506 if (!*(args[cur_arg+1])) {
3507 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
3508 goto error;
3509 }
3510 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003511
Christopher Faulet61cc8522020-04-20 14:54:42 +02003512 port = 0;
3513 release_sample_expr(port_expr);
3514 p = args[cur_arg]; end = p + strlen(p);
3515 port = read_uint(&p, end);
3516 if (p != end) {
3517 int idx = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003518
Christopher Faulet61cc8522020-04-20 14:54:42 +02003519 px->conf.args.ctx = ARGC_SRV;
3520 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3521 file, line, errmsg, &px->conf.args, NULL);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003522
Christopher Faulet61cc8522020-04-20 14:54:42 +02003523 if (!port_expr) {
3524 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
3525 goto error;
3526 }
3527 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3528 memprintf(errmsg, "error detected while parsing port expression : "
3529 " fetch method '%s' extracts information from '%s', "
3530 "none of which is available here.\n",
3531 args[cur_arg], sample_src_names(port_expr->fetch->use));
3532 goto error;
3533 }
3534 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
3535 }
3536 else if (port > 65535 || port < 1) {
3537 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
3538 args[cur_arg]);
3539 goto error;
3540 }
3541 }
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003542 else if (strcmp(args[cur_arg], "proto") == 0) {
3543 if (!*(args[cur_arg+1])) {
3544 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
3545 goto error;
3546 }
3547 mux_proto = get_mux_proto(ist2(args[cur_arg+1], strlen(args[cur_arg+1])));
3548 if (!mux_proto) {
3549 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
3550 goto error;
3551 }
3552 cur_arg++;
3553 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003554 else if (strcmp(args[cur_arg], "comment") == 0) {
3555 if (!*(args[cur_arg+1])) {
3556 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3557 goto error;
3558 }
3559 cur_arg++;
3560 free(comment);
3561 comment = strdup(args[cur_arg]);
3562 if (!comment) {
3563 memprintf(errmsg, "out of memory");
3564 goto error;
3565 }
3566 }
3567 else if (strcmp(args[cur_arg], "send-proxy") == 0)
3568 conn_opts |= TCPCHK_OPT_SEND_PROXY;
3569 else if (strcmp(args[cur_arg], "via-socks4") == 0)
3570 conn_opts |= TCPCHK_OPT_SOCKS4;
3571 else if (strcmp(args[cur_arg], "linger") == 0)
3572 conn_opts |= TCPCHK_OPT_LINGER;
3573#ifdef USE_OPENSSL
3574 else if (strcmp(args[cur_arg], "ssl") == 0) {
3575 px->options |= PR_O_TCPCHK_SSL;
3576 conn_opts |= TCPCHK_OPT_SSL;
3577 }
3578 else if (strcmp(args[cur_arg], "sni") == 0) {
3579 if (!*(args[cur_arg+1])) {
3580 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3581 goto error;
3582 }
3583 cur_arg++;
3584 free(sni);
3585 sni = strdup(args[cur_arg]);
3586 if (!sni) {
3587 memprintf(errmsg, "out of memory");
3588 goto error;
3589 }
3590 }
3591 else if (strcmp(args[cur_arg], "alpn") == 0) {
3592#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
3593 free(alpn);
3594 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
3595 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
3596 goto error;
3597 }
3598 cur_arg++;
3599#else
3600 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003601 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003602#endif
3603 }
3604#endif /* USE_OPENSSL */
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003605
Christopher Faulet61cc8522020-04-20 14:54:42 +02003606 else {
3607 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
3608#ifdef USE_OPENSSL
3609 ", 'ssl', 'sni', 'alpn'"
3610#endif /* USE_OPENSSL */
3611 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
3612 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003613 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003614 }
3615 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003616 }
3617
Christopher Faulet61cc8522020-04-20 14:54:42 +02003618 chk = calloc(1, sizeof(*chk));
3619 if (!chk) {
3620 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003621 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003622 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003623 chk->action = TCPCHK_ACT_CONNECT;
3624 chk->comment = comment;
3625 chk->connect.port = port;
3626 chk->connect.options = conn_opts;
3627 chk->connect.sni = sni;
3628 chk->connect.alpn = alpn;
3629 chk->connect.alpn_len= alpn_len;
3630 chk->connect.port_expr= port_expr;
Christopher Fauletedc6ed92020-04-23 16:27:59 +02003631 chk->connect.mux_proto= mux_proto;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003632 if (sk)
3633 chk->connect.addr = *sk;
3634 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003635
Christopher Faulet61cc8522020-04-20 14:54:42 +02003636 error:
3637 free(alpn);
3638 free(sni);
3639 free(comment);
3640 release_sample_expr(port_expr);
3641 return NULL;
3642}
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003643
Christopher Faulet61cc8522020-04-20 14:54:42 +02003644/* Parses and creates a tcp-check send rule. NULL is returned on error */
3645static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
3646 const char *file, int line, char **errmsg)
3647{
3648 struct tcpcheck_rule *chk = NULL;
3649 char *comment = NULL, *data = NULL;
3650 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003651
Christopher Faulet61cc8522020-04-20 14:54:42 +02003652 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
3653 if (!*(args[cur_arg+1])) {
3654 memprintf(errmsg, "'%s' expects a %s as argument",
3655 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003656 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003657 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003658
Christopher Faulet61cc8522020-04-20 14:54:42 +02003659 data = args[cur_arg+1];
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003660
Christopher Faulet61cc8522020-04-20 14:54:42 +02003661 cur_arg += 2;
3662 while (*(args[cur_arg])) {
3663 if (strcmp(args[cur_arg], "comment") == 0) {
3664 if (!*(args[cur_arg+1])) {
3665 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3666 goto error;
3667 }
3668 cur_arg++;
3669 free(comment);
3670 comment = strdup(args[cur_arg]);
3671 if (!comment) {
3672 memprintf(errmsg, "out of memory");
3673 goto error;
3674 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003675 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003676 else if (strcmp(args[cur_arg], "log-format") == 0) {
3677 if (type == TCPCHK_SEND_BINARY)
3678 type = TCPCHK_SEND_BINARY_LF;
3679 else if (type == TCPCHK_SEND_STRING)
3680 type = TCPCHK_SEND_STRING_LF;
3681 }
3682 else {
3683 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
3684 args[cur_arg]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003685 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003686 }
3687 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003688 }
3689
Christopher Faulet61cc8522020-04-20 14:54:42 +02003690 chk = calloc(1, sizeof(*chk));
3691 if (!chk) {
3692 memprintf(errmsg, "out of memory");
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003693 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003694 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003695 chk->action = TCPCHK_ACT_SEND;
3696 chk->comment = comment;
3697 chk->send.type = type;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003698
Christopher Faulet61cc8522020-04-20 14:54:42 +02003699 switch (chk->send.type) {
3700 case TCPCHK_SEND_STRING:
3701 chk->send.data = ist2(strdup(data), strlen(data));
3702 if (!isttest(chk->send.data)) {
3703 memprintf(errmsg, "out of memory");
3704 goto error;
3705 }
3706 break;
3707 case TCPCHK_SEND_BINARY:
3708 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
3709 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
3710 goto error;
3711 }
3712 break;
3713 case TCPCHK_SEND_STRING_LF:
3714 case TCPCHK_SEND_BINARY_LF:
3715 LIST_INIT(&chk->send.fmt);
3716 px->conf.args.ctx = ARGC_SRV;
3717 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3718 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
3719 goto error;
3720 }
3721 break;
3722 case TCPCHK_SEND_HTTP:
3723 case TCPCHK_SEND_UNDEF:
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003724 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003725 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003726
Christopher Faulet61cc8522020-04-20 14:54:42 +02003727 return chk;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003728
Christopher Faulet61cc8522020-04-20 14:54:42 +02003729 error:
3730 free(chk);
3731 free(comment);
3732 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003733}
3734
Christopher Faulet61cc8522020-04-20 14:54:42 +02003735/* Parses and creates a http-check send rule. NULL is returned on error */
3736static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
3737 const char *file, int line, char **errmsg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003738{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003739 struct tcpcheck_rule *chk = NULL;
3740 struct tcpcheck_http_hdr *hdr = NULL;
3741 struct http_hdr hdrs[global.tune.max_http_hdr];
3742 char *meth = NULL, *uri = NULL, *vsn = NULL;
3743 char *body = NULL, *comment = NULL;
3744 unsigned int flags = 0;
3745 int i = 0;
3746
3747 cur_arg++;
3748 while (*(args[cur_arg])) {
3749 if (strcmp(args[cur_arg], "meth") == 0) {
3750 if (!*(args[cur_arg+1])) {
3751 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3752 goto error;
3753 }
3754 cur_arg++;
3755 meth = args[cur_arg];
3756 }
3757 else if (strcmp(args[cur_arg], "uri") == 0) {
3758 if (!*(args[cur_arg+1])) {
3759 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3760 goto error;
3761 }
3762 cur_arg++;
3763 uri = args[cur_arg];
3764 // TODO: log-format uri
3765 }
3766 else if (strcmp(args[cur_arg], "vsn") == 0) {
3767 if (!*(args[cur_arg+1])) {
3768 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3769 goto error;
3770 }
3771 cur_arg++;
3772 vsn = args[cur_arg];
3773 }
3774 else if (strcmp(args[cur_arg], "hdr") == 0) {
3775 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
3776 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
3777 goto error;
3778 }
3779 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
3780 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
3781 i++;
3782 cur_arg += 2;
3783 }
3784 else if (strcmp(args[cur_arg], "body") == 0) {
3785 if (!*(args[cur_arg+1])) {
3786 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3787 goto error;
3788 }
3789 cur_arg++;
3790 body = args[cur_arg];
3791 // TODO: log-format body
3792 }
3793 else if (strcmp(args[cur_arg], "comment") == 0) {
3794 if (!*(args[cur_arg+1])) {
3795 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
3796 goto error;
3797 }
3798 cur_arg++;
3799 free(comment);
3800 comment = strdup(args[cur_arg]);
3801 if (!comment) {
3802 memprintf(errmsg, "out of memory");
3803 goto error;
3804 }
3805 }
3806 else {
3807 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'hdr' and 'body' but got '%s' as argument.",
3808 args[cur_arg]);
3809 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003810 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003811 cur_arg++;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003812 }
3813
Christopher Faulet61cc8522020-04-20 14:54:42 +02003814 hdrs[i].n = hdrs[i].v = IST_NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003815
Christopher Faulet61cc8522020-04-20 14:54:42 +02003816 chk = calloc(1, sizeof(*chk));
3817 if (!chk) {
3818 memprintf(errmsg, "out of memory");
3819 goto error;
3820 }
3821 chk->action = TCPCHK_ACT_SEND;
3822 chk->comment = comment; comment = NULL;
3823 chk->send.type = TCPCHK_SEND_HTTP;
3824 chk->send.http.flags = flags;
3825 LIST_INIT(&chk->send.http.hdrs);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003826
Christopher Faulet61cc8522020-04-20 14:54:42 +02003827 if (meth) {
3828 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
3829 chk->send.http.meth.str.area = strdup(meth);
3830 chk->send.http.meth.str.data = strlen(meth);
3831 if (!chk->send.http.meth.str.area) {
3832 memprintf(errmsg, "out of memory");
3833 goto error;
3834 }
3835 }
3836 if (uri) {
3837 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
3838 if (!isttest(chk->send.http.uri)) {
3839 memprintf(errmsg, "out of memory");
3840 goto error;
3841 }
3842 }
3843 if (vsn) {
3844 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
3845 if (!isttest(chk->send.http.vsn)) {
3846 memprintf(errmsg, "out of memory");
3847 goto error;
3848 }
3849 }
Christopher Fauletb61caf42020-04-21 10:57:42 +02003850 for (i = 0; istlen(hdrs[i].n); i++) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003851 hdr = calloc(1, sizeof(*hdr));
3852 if (!hdr) {
3853 memprintf(errmsg, "out of memory");
3854 goto error;
3855 }
3856 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02003857 hdr->name = istdup(hdrs[i].n);
3858 if (!isttest(hdr->name)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02003859 memprintf(errmsg, "out of memory");
3860 goto error;
3861 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003862
Christopher Fauletb61caf42020-04-21 10:57:42 +02003863 ist0(hdrs[i].v);
3864 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 +02003865 goto error;
3866 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
3867 hdr = NULL;
3868 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003869
Christopher Faulet61cc8522020-04-20 14:54:42 +02003870 if (body) {
3871 chk->send.http.body = ist2(strdup(body), strlen(body));
3872 if (!isttest(chk->send.http.body)) {
3873 memprintf(errmsg, "out of memory");
3874 goto error;
3875 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003876 }
3877
Christopher Faulet61cc8522020-04-20 14:54:42 +02003878 return chk;
3879
3880 error:
3881 free_tcpcheck_http_hdr(hdr);
3882 free_tcpcheck(chk, 0);
3883 free(comment);
3884 return NULL;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003885}
3886
Christopher Faulet61cc8522020-04-20 14:54:42 +02003887/* Parses and creates a http-check comment rule. NULL is returned on error */
3888static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
3889 const char *file, int line, char **errmsg)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003890{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003891 struct tcpcheck_rule *chk = NULL;
3892 char *comment = NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003893
Christopher Faulet61cc8522020-04-20 14:54:42 +02003894 if (!*(args[cur_arg+1])) {
3895 memprintf(errmsg, "expects a string as argument");
3896 goto error;
3897 }
3898 cur_arg++;
3899 comment = strdup(args[cur_arg]);
3900 if (!comment) {
3901 memprintf(errmsg, "out of memory");
3902 goto error;
3903 }
Willy Tarreau04276f32017-01-06 17:41:29 +01003904
Christopher Faulet61cc8522020-04-20 14:54:42 +02003905 chk = calloc(1, sizeof(*chk));
3906 if (!chk) {
3907 memprintf(errmsg, "out of memory");
3908 goto error;
3909 }
3910 chk->action = TCPCHK_ACT_COMMENT;
3911 chk->comment = comment;
3912 return chk;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003913
Christopher Faulet61cc8522020-04-20 14:54:42 +02003914 error:
3915 free(comment);
3916 return NULL;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003917}
3918
Christopher Faulet61cc8522020-04-20 14:54:42 +02003919/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
3920 * on error. <proto> is set to the right protocol flags (covered by the
3921 * TCPCHK_RULES_PROTO_CHK mask).
3922 */
3923static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
3924 struct list *rules, unsigned int proto,
3925 const char *file, int line, char **errmsg)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003926{
Christopher Faulet61cc8522020-04-20 14:54:42 +02003927 struct tcpcheck_rule *prev_check, *chk = NULL;
3928 struct sample_expr *status_expr = NULL;
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003929 char *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003930 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
3931 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
3932 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
3933 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
3934 long min_recv = -1;
Christopher Faulet88d939c2020-04-22 15:32:11 +02003935 int inverse = 0;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003936
Christopher Faulet8021a5f2020-04-24 13:53:12 +02003937 on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet61cc8522020-04-20 14:54:42 +02003938 if (!*(args[cur_arg+1])) {
3939 memprintf(errmsg, "expects at least a matching pattern as arguments");
3940 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003941 }
3942
Christopher Faulet61cc8522020-04-20 14:54:42 +02003943 cur_arg++;
3944 while (*(args[cur_arg])) {
3945 int in_pattern = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02003946
Christopher Faulet61cc8522020-04-20 14:54:42 +02003947 rescan:
3948 if (strcmp(args[cur_arg], "min-recv") == 0) {
3949 if (in_pattern) {
3950 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3951 goto error;
3952 }
3953 if (!*(args[cur_arg+1])) {
3954 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
3955 goto error;
3956 }
3957 /* Use an signed integer here because of chksize */
3958 cur_arg++;
3959 min_recv = atol(args[cur_arg]);
3960 if (min_recv < -1 || min_recv > INT_MAX) {
3961 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
3962 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02003963 }
3964 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003965 else if (*(args[cur_arg]) == '!') {
3966 in_pattern = 1;
3967 while (*(args[cur_arg]) == '!') {
3968 inverse = !inverse;
3969 args[cur_arg]++;
Christopher Faulete5870d82020-04-15 11:32:03 +02003970 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003971 if (!*(args[cur_arg]))
3972 cur_arg++;
3973 goto rescan;
Christopher Faulete5870d82020-04-15 11:32:03 +02003974 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003975 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
3976 if (type != TCPCHK_EXPECT_UNDEF) {
3977 memprintf(errmsg, "only on pattern expected");
3978 goto error;
3979 }
3980 if (proto != TCPCHK_RULES_HTTP_CHK)
3981 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
3982 else
3983 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Faulete5870d82020-04-15 11:32:03 +02003984
Christopher Faulet61cc8522020-04-20 14:54:42 +02003985 if (!*(args[cur_arg+1])) {
3986 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3987 goto error;
3988 }
3989 cur_arg++;
3990 pattern = args[cur_arg];
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003991 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02003992 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
3993 if (proto == TCPCHK_RULES_HTTP_CHK)
3994 goto bad_http_kw;
3995 if (type != TCPCHK_EXPECT_UNDEF) {
3996 memprintf(errmsg, "only on pattern expected");
3997 goto error;
3998 }
3999 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004000
Christopher Faulet61cc8522020-04-20 14:54:42 +02004001 if (!*(args[cur_arg+1])) {
4002 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4003 goto error;
4004 }
4005 cur_arg++;
4006 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004007 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004008 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
4009 if (proto != TCPCHK_RULES_HTTP_CHK)
4010 goto bad_tcp_kw;
4011 if (type != TCPCHK_EXPECT_UNDEF) {
4012 memprintf(errmsg, "only on pattern expected");
4013 goto error;
4014 }
4015 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004016
Christopher Faulet61cc8522020-04-20 14:54:42 +02004017 if (!*(args[cur_arg+1])) {
4018 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4019 goto error;
4020 }
4021 cur_arg++;
4022 pattern = args[cur_arg];
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004023 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004024 else if (strcmp(args[cur_arg], "custom") == 0) {
4025 if (in_pattern) {
4026 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4027 goto error;
4028 }
4029 if (type != TCPCHK_EXPECT_UNDEF) {
4030 memprintf(errmsg, "only on pattern expected");
4031 goto error;
4032 }
4033 type = TCPCHK_EXPECT_CUSTOM;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004034 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004035 else if (strcmp(args[cur_arg], "comment") == 0) {
4036 if (in_pattern) {
4037 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4038 goto error;
4039 }
4040 if (!*(args[cur_arg+1])) {
4041 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4042 goto error;
4043 }
4044 cur_arg++;
4045 free(comment);
4046 comment = strdup(args[cur_arg]);
4047 if (!comment) {
4048 memprintf(errmsg, "out of memory");
4049 goto error;
4050 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004051 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004052 else if (strcmp(args[cur_arg], "on-success") == 0) {
4053 if (in_pattern) {
4054 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4055 goto error;
4056 }
4057 if (!*(args[cur_arg+1])) {
4058 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4059 goto error;
4060 }
4061 cur_arg++;
4062 free(on_success_msg);
4063 on_success_msg = strdup(args[cur_arg]);
4064 if (!on_success_msg) {
4065 memprintf(errmsg, "out of memory");
4066 goto error;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004067 }
4068 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004069 else if (strcmp(args[cur_arg], "on-error") == 0) {
4070 if (in_pattern) {
4071 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4072 goto error;
4073 }
4074 if (!*(args[cur_arg+1])) {
4075 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4076 goto error;
4077 }
4078 cur_arg++;
4079 free(on_error_msg);
4080 on_error_msg = strdup(args[cur_arg]);
4081 if (!on_error_msg) {
4082 memprintf(errmsg, "out of memory");
4083 goto error;
4084 }
4085 }
4086 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4087 if (in_pattern) {
4088 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4089 goto error;
4090 }
4091 if (!*(args[cur_arg+1])) {
4092 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4093 goto error;
4094 }
4095 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4096 ok_st = HCHK_STATUS_L7OKD;
4097 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4098 ok_st = HCHK_STATUS_L7OKCD;
4099 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4100 ok_st = HCHK_STATUS_L6OK;
4101 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4102 ok_st = HCHK_STATUS_L4OK;
4103 else {
4104 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4105 args[cur_arg], args[cur_arg+1]);
4106 goto error;
4107 }
4108 cur_arg++;
4109 }
4110 else if (strcmp(args[cur_arg], "error-status") == 0) {
4111 if (in_pattern) {
4112 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4113 goto error;
4114 }
4115 if (!*(args[cur_arg+1])) {
4116 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4117 goto error;
4118 }
4119 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4120 err_st = HCHK_STATUS_L7RSP;
4121 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4122 err_st = HCHK_STATUS_L7STS;
4123 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4124 err_st = HCHK_STATUS_L6RSP;
4125 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4126 err_st = HCHK_STATUS_L4CON;
4127 else {
4128 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4129 args[cur_arg], args[cur_arg+1]);
4130 goto error;
4131 }
4132 cur_arg++;
4133 }
4134 else if (strcmp(args[cur_arg], "status-code") == 0) {
4135 int idx = 0;
Christopher Fauletd7e63962020-04-17 20:15:59 +02004136
Christopher Faulet61cc8522020-04-20 14:54:42 +02004137 if (in_pattern) {
4138 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4139 goto error;
4140 }
4141 if (!*(args[cur_arg+1])) {
4142 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4143 goto error;
4144 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004145
Christopher Faulet61cc8522020-04-20 14:54:42 +02004146 cur_arg++;
4147 release_sample_expr(status_expr);
4148 px->conf.args.ctx = ARGC_SRV;
4149 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4150 file, line, errmsg, &px->conf.args, NULL);
4151 if (!status_expr) {
4152 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4153 goto error;
4154 }
4155 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4156 memprintf(errmsg, "error detected while parsing status-code expression : "
4157 " fetch method '%s' extracts information from '%s', "
4158 "none of which is available here.\n",
4159 args[cur_arg], sample_src_names(status_expr->fetch->use));
4160 goto error;
4161 }
4162 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
4163 }
4164 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4165 if (in_pattern) {
4166 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4167 goto error;
4168 }
4169 if (!*(args[cur_arg+1])) {
4170 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4171 goto error;
4172 }
4173 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4174 tout_st = HCHK_STATUS_L7TOUT;
4175 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4176 tout_st = HCHK_STATUS_L6TOUT;
4177 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4178 tout_st = HCHK_STATUS_L4TOUT;
4179 else {
4180 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4181 args[cur_arg], args[cur_arg+1]);
4182 goto error;
4183 }
4184 cur_arg++;
4185 }
4186 else {
4187 if (proto == TCPCHK_RULES_HTTP_CHK) {
4188 bad_http_kw:
4189 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
4190 " or comment but got '%s' as argument.", args[cur_arg]);
4191 }
4192 else {
4193 bad_tcp_kw:
4194 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4195 " or comment but got '%s' as argument.", args[cur_arg]);
4196 }
4197 goto error;
4198 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004199
Christopher Faulet61cc8522020-04-20 14:54:42 +02004200 cur_arg++;
4201 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004202
Christopher Faulet61cc8522020-04-20 14:54:42 +02004203 chk = calloc(1, sizeof(*chk));
4204 if (!chk) {
4205 memprintf(errmsg, "out of memory");
4206 goto error;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004207 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004208 chk->action = TCPCHK_ACT_EXPECT;
4209 LIST_INIT(&chk->expect.onerror_fmt);
4210 LIST_INIT(&chk->expect.onsuccess_fmt);
4211 chk->comment = comment; comment = NULL;
4212 chk->expect.type = type;
4213 chk->expect.min_recv = min_recv;
4214 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004215 chk->expect.ok_status = ok_st;
4216 chk->expect.err_status = err_st;
4217 chk->expect.tout_status = tout_st;
4218 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004219
Christopher Faulet61cc8522020-04-20 14:54:42 +02004220 if (on_success_msg) {
4221 px->conf.args.ctx = ARGC_SRV;
4222 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4223 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4224 goto error;
4225 }
4226 free(on_success_msg);
4227 on_success_msg = NULL;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004228 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004229 if (on_error_msg) {
4230 px->conf.args.ctx = ARGC_SRV;
4231 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4232 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4233 goto error;
4234 }
4235 free(on_error_msg);
4236 on_error_msg = NULL;
4237 }
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004238
Christopher Faulet61cc8522020-04-20 14:54:42 +02004239 switch (chk->expect.type) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004240 case TCPCHK_EXPECT_HTTP_STATUS: {
4241 const char *p = pattern;
4242 unsigned int c1,c2;
4243
4244 chk->expect.codes.codes = NULL;
4245 chk->expect.codes.num = 0;
4246 while (1) {
4247 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
4248 if (*p == '-') {
4249 p++;
4250 c2 = read_uint(&p, pattern + strlen(pattern));
4251 }
4252 if (c1 > c2) {
4253 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
4254 goto error;
4255 }
4256
4257 chk->expect.codes.num++;
4258 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
4259 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
4260 if (!chk->expect.codes.codes) {
4261 memprintf(errmsg, "out of memory");
4262 goto error;
4263 }
4264 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
4265 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
4266
4267 if (*p == '\0')
4268 break;
4269 if (*p != ',') {
4270 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
4271 goto error;
4272 }
4273 p++;
4274 }
4275 break;
4276 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004277 case TCPCHK_EXPECT_STRING:
Christopher Faulet61cc8522020-04-20 14:54:42 +02004278 case TCPCHK_EXPECT_HTTP_BODY:
4279 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
Christopher Fauletb61caf42020-04-21 10:57:42 +02004280 if (!isttest(chk->expect.data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02004281 memprintf(errmsg, "out of memory");
4282 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004283 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004284 break;
4285 case TCPCHK_EXPECT_BINARY:
4286 if (parse_binary(pattern, &chk->expect.data.ptr, (int *)&chk->expect.data.len, errmsg) == 0) {
4287 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4288 goto error;
4289 }
4290 case TCPCHK_EXPECT_REGEX:
4291 case TCPCHK_EXPECT_REGEX_BINARY:
4292 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
4293 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Faulet88d939c2020-04-22 15:32:11 +02004294 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004295 if (!chk->expect.regex)
4296 goto error;
4297 break;
4298 case TCPCHK_EXPECT_CUSTOM:
4299 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4300 break;
4301 case TCPCHK_EXPECT_UNDEF:
4302 free(chk);
4303 memprintf(errmsg, "pattern not found");
4304 goto error;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004305 }
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004306
Christopher Faulet61cc8522020-04-20 14:54:42 +02004307 /* All tcp-check expect points back to the first inverse expect rule in
4308 * a chain of one or more expect rule, potentially itself.
4309 */
4310 chk->expect.head = chk;
4311 list_for_each_entry_rev(prev_check, rules, list) {
4312 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4313 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
4314 chk->expect.head = prev_check;
4315 continue;
4316 }
4317 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
4318 break;
Christopher Faulet404f9192020-04-09 23:13:54 +02004319 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004320 return chk;
4321
4322 error:
4323 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004324 free(comment);
4325 free(on_success_msg);
4326 free(on_error_msg);
4327 release_sample_expr(status_expr);
Christopher Faulet404f9192020-04-09 23:13:54 +02004328 return NULL;
4329}
4330
Christopher Faulet61cc8522020-04-20 14:54:42 +02004331/* Overwrites fields of the old http send rule with those of the new one. When
4332 * replaced, old values are freed and replaced by the new ones. New values are
4333 * not copied but transferred. At the end <new> should be empty and can be
4334 * safely released. This function never fails.
4335 */
4336static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
Christopher Faulet404f9192020-04-09 23:13:54 +02004337{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004338 struct logformat_node *lf, *lfb;
4339 struct tcpcheck_http_hdr *hdr, *bhdr;
Christopher Faulet404f9192020-04-09 23:13:54 +02004340
Christopher Faulet404f9192020-04-09 23:13:54 +02004341
Christopher Faulet61cc8522020-04-20 14:54:42 +02004342 if (new->send.http.meth.str.area) {
4343 free(old->send.http.meth.str.area);
4344 old->send.http.meth.meth = new->send.http.meth.meth;
4345 old->send.http.meth.str.area = new->send.http.meth.str.area;
4346 old->send.http.meth.str.data = new->send.http.meth.str.data;
4347 new->send.http.meth.str = BUF_NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02004348 }
4349
Christopher Faulet61cc8522020-04-20 14:54:42 +02004350 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4351 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004352 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004353 else
4354 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4355 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4356 old->send.http.uri = new->send.http.uri;
4357 new->send.http.uri = IST_NULL;
4358 }
4359 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4360 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004361 istfree(&old->send.http.uri);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004362 else
4363 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4364 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4365 LIST_INIT(&old->send.http.uri_fmt);
4366 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4367 LIST_DEL(&lf->list);
4368 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4369 }
4370 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004371
Christopher Faulet61cc8522020-04-20 14:54:42 +02004372 if (isttest(new->send.http.vsn)) {
Christopher Fauletb61caf42020-04-21 10:57:42 +02004373 istfree(&old->send.http.vsn);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004374 old->send.http.vsn = new->send.http.vsn;
4375 new->send.http.vsn = IST_NULL;
4376 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004377
Christopher Faulet61cc8522020-04-20 14:54:42 +02004378 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4379 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4380 LIST_DEL(&hdr->list);
4381 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
Christopher Faulet404f9192020-04-09 23:13:54 +02004382 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004383
4384 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4385 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004386 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004387 else
4388 free_tcpcheck_fmt(&old->send.http.body_fmt);
4389 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4390 old->send.http.body = new->send.http.body;
4391 new->send.http.body = IST_NULL;
4392 }
4393 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4394 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
Christopher Fauletb61caf42020-04-21 10:57:42 +02004395 istfree(&old->send.http.body);
Christopher Faulet61cc8522020-04-20 14:54:42 +02004396 else
4397 free_tcpcheck_fmt(&old->send.http.body_fmt);
4398 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4399 LIST_INIT(&old->send.http.body_fmt);
4400 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4401 LIST_DEL(&lf->list);
4402 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4403 }
4404 }
Christopher Faulet404f9192020-04-09 23:13:54 +02004405}
4406
Christopher Faulet61cc8522020-04-20 14:54:42 +02004407/* Internal function used to add an http-check rule in a list during the config
4408 * parsing step. Depending on its type, and the previously inserted rules, a
4409 * specific action may be performed or an error may be reported. This functions
4410 * returns 1 on success and 0 on error and <errmsg> is filled with the error
4411 * message.
4412 */
4413static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004414{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004415 struct tcpcheck_rule *r;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004416
Christopher Faulet61cc8522020-04-20 14:54:42 +02004417 /* the implicit send rule coming from an "option httpchk" line must be
4418 * merged with the first explici http-check send rule, if
4419 * any. Depdending the declaration order some tests are required.
4420 *
4421 * Some tests is also required for other kinds of http-check rules to be
4422 * sure the ruleset remains valid.
4423 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004424
Christopher Faulet61cc8522020-04-20 14:54:42 +02004425 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4426 /* Tries to add an implcit http-check send rule from an "option httpchk" line.
4427 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
4428 * following tests are performed :
4429 *
4430 * 1- If there is no such rule or if it is not a send rule, the implicit send
4431 * rule is pushed in front of the ruleset
4432 *
4433 * 2- If it is another implicit send rule, it is replaced with the new one.
4434 *
4435 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
4436 * both, overwritting the old send rule (the explicit one) with info of the
4437 * new send rule (the implicit one).
4438 */
4439 r = get_first_tcpcheck_rule(rules);
4440 if (r && r->action == TCPCHK_ACT_CONNECT)
4441 r = get_next_tcpcheck_rule(rules, r);
4442 if (!r || r->action != TCPCHK_ACT_SEND)
4443 LIST_ADD(rules->list, &chk->list);
4444 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
4445 LIST_DEL(&r->list);
4446 free_tcpcheck(r, 0);
4447 LIST_ADD(rules->list, &chk->list);
4448 }
4449 else {
4450 tcpcheck_overwrite_send_http_rule(r, chk);
4451 free_tcpcheck(chk, 0);
4452 }
4453 }
4454 else {
4455 /* Tries to add an explicit http-check rule. First of all we check the typefo the
4456 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
4457 * with an existing implicit send rule, if any. At the end, if there is no error,
4458 * the rule is appended to the list.
4459 */
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004460
Christopher Faulet61cc8522020-04-20 14:54:42 +02004461 r = get_last_tcpcheck_rule(rules);
4462 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4463 /* no error */;
4464 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
4465 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
4466 chk->index+1);
4467 return 0;
4468 }
4469 else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
4470 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
4471 chk->index+1);
4472 return 0;
4473 }
4474 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
4475 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
4476 chk->index+1);
4477 return 0;
4478 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004479
Christopher Faulet61cc8522020-04-20 14:54:42 +02004480 if (chk->action == TCPCHK_ACT_SEND) {
4481 r = get_first_tcpcheck_rule(rules);
4482 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4483 tcpcheck_overwrite_send_http_rule(r, chk);
4484 free_tcpcheck(chk, 0);
4485 LIST_DEL(&r->list);
4486 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
4487 chk = r;
4488 }
4489 }
4490 LIST_ADDQ(rules->list, &chk->list);
4491 }
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004492 return 1;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004493}
4494
Christopher Faulet61cc8522020-04-20 14:54:42 +02004495/**************************************************************************/
4496/************************** Init/deinit checks ****************************/
4497/**************************************************************************/
4498static const char *init_check(struct check *check, int type)
4499{
4500 check->type = type;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004501
Christopher Faulet61cc8522020-04-20 14:54:42 +02004502 b_reset(&check->bi); check->bi.size = global.tune.chksize;
4503 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004504
Christopher Faulet61cc8522020-04-20 14:54:42 +02004505 check->bi.area = calloc(check->bi.size, sizeof(char));
4506 check->bo.area = calloc(check->bo.size, sizeof(char));
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004507
Christopher Faulet61cc8522020-04-20 14:54:42 +02004508 if (!check->bi.area || !check->bo.area)
4509 return "out of memory while allocating check buffer";
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004510
Christopher Faulet61cc8522020-04-20 14:54:42 +02004511 check->wait_list.tasklet = tasklet_new();
4512 if (!check->wait_list.tasklet)
4513 return "out of memory while allocating check tasklet";
4514 check->wait_list.events = 0;
4515 check->wait_list.tasklet->process = event_srv_chk_io;
4516 check->wait_list.tasklet->context = check;
4517 return NULL;
4518}
4519
4520void free_check(struct check *check)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004521{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004522 task_destroy(check->task);
4523 if (check->wait_list.tasklet)
4524 tasklet_free(check->wait_list.tasklet);
4525
4526 free(check->bi.area);
4527 free(check->bo.area);
4528 if (check->cs) {
4529 free(check->cs->conn);
4530 check->cs->conn = NULL;
4531 cs_free(check->cs);
4532 check->cs = NULL;
4533 }
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004534}
4535
Christopher Faulet61cc8522020-04-20 14:54:42 +02004536/* manages a server health-check. Returns the time the task accepts to wait, or
4537 * TIME_ETERNITY for infinity.
4538 */
4539static struct task *process_chk(struct task *t, void *context, unsigned short state)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004540{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004541 struct check *check = context;
4542
4543 if (check->type == PR_O2_EXT_CHK)
4544 return process_chk_proc(t, context, state);
4545 return process_chk_conn(t, context, state);
4546
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004547}
4548
Christopher Faulet61cc8522020-04-20 14:54:42 +02004549
4550static int start_check_task(struct check *check, int mininter,
4551 int nbcheck, int srvpos)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004552{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004553 struct task *t;
4554 unsigned long thread_mask = MAX_THREADS_MASK;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004555
Christopher Faulet61cc8522020-04-20 14:54:42 +02004556 if (check->type == PR_O2_EXT_CHK)
4557 thread_mask = 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004558
Christopher Faulet61cc8522020-04-20 14:54:42 +02004559 /* task for the check */
4560 if ((t = task_new(thread_mask)) == NULL) {
4561 ha_alert("Starting [%s:%s] check: out of memory.\n",
4562 check->server->proxy->id, check->server->id);
4563 return 0;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004564 }
4565
Christopher Faulet61cc8522020-04-20 14:54:42 +02004566 check->task = t;
4567 t->process = process_chk;
4568 t->context = check;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004569
Christopher Faulet61cc8522020-04-20 14:54:42 +02004570 if (mininter < srv_getinter(check))
4571 mininter = srv_getinter(check);
4572
4573 if (global.max_spread_checks && mininter > global.max_spread_checks)
4574 mininter = global.max_spread_checks;
4575
4576 /* check this every ms */
4577 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
4578 check->start = now;
4579 task_queue(t);
4580
4581 return 1;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004582}
4583
Christopher Faulet61cc8522020-04-20 14:54:42 +02004584/* updates the server's weight during a warmup stage. Once the final weight is
4585 * reached, the task automatically stops. Note that any server status change
4586 * must have updated s->last_change accordingly.
4587 */
4588static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004589{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004590 struct server *s = context;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004591
Christopher Faulet61cc8522020-04-20 14:54:42 +02004592 /* by default, plan on stopping the task */
4593 t->expire = TICK_ETERNITY;
4594 if ((s->next_admin & SRV_ADMF_MAINT) ||
4595 (s->next_state != SRV_ST_STARTING))
4596 return t;
Christopher Faulete5870d82020-04-15 11:32:03 +02004597
Christopher Faulet61cc8522020-04-20 14:54:42 +02004598 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004599
Christopher Faulet61cc8522020-04-20 14:54:42 +02004600 /* recalculate the weights and update the state */
4601 server_recalc_eweight(s, 1);
Christopher Faulet5c288742020-03-31 08:15:58 +02004602
Christopher Faulet61cc8522020-04-20 14:54:42 +02004603 /* probably that we can refill this server with a bit more connections */
4604 pendconn_grab_from_px(s);
Christopher Faulet5c288742020-03-31 08:15:58 +02004605
Christopher Faulet61cc8522020-04-20 14:54:42 +02004606 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
Christopher Faulet5c288742020-03-31 08:15:58 +02004607
Christopher Faulet61cc8522020-04-20 14:54:42 +02004608 /* get back there in 1 second or 1/20th of the slowstart interval,
4609 * whichever is greater, resulting in small 5% steps.
4610 */
4611 if (s->next_state == SRV_ST_STARTING)
4612 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
4613 return t;
4614}
4615
4616/*
4617 * Start health-check.
4618 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
4619 */
4620static int start_checks()
4621{
4622
4623 struct proxy *px;
4624 struct server *s;
4625 struct task *t;
4626 int nbcheck=0, mininter=0, srvpos=0;
4627
4628 /* 0- init the dummy frontend used to create all checks sessions */
4629 init_new_proxy(&checks_fe);
4630 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
4631 checks_fe.mode = PR_MODE_TCP;
4632 checks_fe.maxconn = 0;
4633 checks_fe.conn_retries = CONN_RETRIES;
4634 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
4635 checks_fe.timeout.client = TICK_ETERNITY;
4636
4637 /* 1- count the checkers to run simultaneously.
4638 * We also determine the minimum interval among all of those which
4639 * have an interval larger than SRV_CHK_INTER_THRES. This interval
4640 * will be used to spread their start-up date. Those which have
4641 * a shorter interval will start independently and will not dictate
4642 * too short an interval for all others.
4643 */
4644 for (px = proxies_list; px; px = px->next) {
4645 for (s = px->srv; s; s = s->next) {
4646 if (s->slowstart) {
4647 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
4648 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
4649 return ERR_ALERT | ERR_FATAL;
4650 }
4651 /* We need a warmup task that will be called when the server
4652 * state switches from down to up.
4653 */
4654 s->warmup = t;
4655 t->process = server_warmup;
4656 t->context = s;
4657 /* server can be in this state only because of */
4658 if (s->next_state == SRV_ST_STARTING)
4659 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 +02004660 }
4661
Christopher Faulet61cc8522020-04-20 14:54:42 +02004662 if (s->check.state & CHK_ST_CONFIGURED) {
4663 nbcheck++;
4664 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
4665 (!mininter || mininter > srv_getinter(&s->check)))
4666 mininter = srv_getinter(&s->check);
Christopher Faulet5c288742020-03-31 08:15:58 +02004667 }
4668
Christopher Faulet61cc8522020-04-20 14:54:42 +02004669 if (s->agent.state & CHK_ST_CONFIGURED) {
4670 nbcheck++;
4671 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
4672 (!mininter || mininter > srv_getinter(&s->agent)))
4673 mininter = srv_getinter(&s->agent);
4674 }
Christopher Faulet5c288742020-03-31 08:15:58 +02004675 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004676 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004677
Christopher Faulet61cc8522020-04-20 14:54:42 +02004678 if (!nbcheck)
4679 return 0;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004680
Christopher Faulet61cc8522020-04-20 14:54:42 +02004681 srand((unsigned)time(NULL));
Christopher Fauletb7d30092020-03-30 15:19:03 +02004682
Christopher Faulet61cc8522020-04-20 14:54:42 +02004683 /*
4684 * 2- start them as far as possible from each others. For this, we will
4685 * start them after their interval set to the min interval divided by
4686 * the number of servers, weighted by the server's position in the list.
4687 */
4688 for (px = proxies_list; px; px = px->next) {
4689 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
4690 if (init_pid_list()) {
4691 ha_alert("Starting [%s] check: out of memory.\n", px->id);
4692 return ERR_ALERT | ERR_FATAL;
4693 }
4694 }
Christopher Fauletb7d30092020-03-30 15:19:03 +02004695
Christopher Faulet61cc8522020-04-20 14:54:42 +02004696 for (s = px->srv; s; s = s->next) {
4697 /* A task for the main check */
4698 if (s->check.state & CHK_ST_CONFIGURED) {
4699 if (s->check.type == PR_O2_EXT_CHK) {
4700 if (!prepare_external_check(&s->check))
4701 return ERR_ALERT | ERR_FATAL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004702 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004703 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
4704 return ERR_ALERT | ERR_FATAL;
4705 srvpos++;
Christopher Faulet98572322020-03-30 13:16:44 +02004706 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004707
Christopher Faulet61cc8522020-04-20 14:54:42 +02004708 /* A task for a auxiliary agent check */
4709 if (s->agent.state & CHK_ST_CONFIGURED) {
4710 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
4711 return ERR_ALERT | ERR_FATAL;
4712 }
4713 srvpos++;
4714 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004715 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004716 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004717 return 0;
4718}
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004719
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004720
Christopher Faulet61cc8522020-04-20 14:54:42 +02004721/*
4722 * Return value:
4723 * the port to be used for the health check
4724 * 0 in case no port could be found for the check
4725 */
4726static int srv_check_healthcheck_port(struct check *chk)
4727{
4728 int i = 0;
4729 struct server *srv = NULL;
4730
4731 srv = chk->server;
4732
4733 /* by default, we use the health check port ocnfigured */
4734 if (chk->port > 0)
4735 return chk->port;
4736
4737 /* try to get the port from check_core.addr if check.port not set */
4738 i = get_host_port(&chk->addr);
4739 if (i > 0)
4740 return i;
4741
4742 /* try to get the port from server address */
4743 /* prevent MAPPORTS from working at this point, since checks could
4744 * not be performed in such case (MAPPORTS impose a relative ports
4745 * based on live traffic)
4746 */
4747 if (srv->flags & SRV_F_MAPPORTS)
4748 return 0;
4749
4750 i = srv->svc_port; /* by default */
4751 if (i > 0)
4752 return i;
4753
4754 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004755}
4756
Christopher Faulet61cc8522020-04-20 14:54:42 +02004757/* Initializes an health-check attached to the server <srv>. Non-zero is returned
4758 * if an error occurred.
4759 */
4760static int init_srv_check(struct server *srv)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004761{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004762 const char *err;
4763 struct tcpcheck_rule *r;
4764 int ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004765
Christopher Faulet61cc8522020-04-20 14:54:42 +02004766 if (!srv->do_check)
4767 goto out;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004768
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004769
Christopher Faulet61cc8522020-04-20 14:54:42 +02004770 /* If neither a port nor an addr was specified and no check transport
4771 * layer is forced, then the transport layer used by the checks is the
4772 * same as for the production traffic. Otherwise we use raw_sock by
4773 * default, unless one is specified.
4774 */
4775 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4776 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4777 srv->check.use_ssl = srv->use_ssl;
4778 srv->check.xprt = srv->xprt;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004779 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004780 else if (srv->check.use_ssl == 1)
4781 srv->check.xprt = xprt_get(XPRT_SSL);
4782 srv->check.send_proxy |= (srv->pp_opts);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004783 }
4784
Christopher Faulet12882cf2020-04-23 15:50:18 +02004785 /* Inherit the mux protocol from the server if not already defined for
4786 * the check
4787 */
4788 if (srv->mux_proto && !srv->check.mux_proto)
4789 srv->check.mux_proto = srv->mux_proto;
4790
Christopher Faulet61cc8522020-04-20 14:54:42 +02004791 /* validate <srv> server health-check settings */
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004792
Christopher Faulet61cc8522020-04-20 14:54:42 +02004793 /* We need at least a service port, a check port or the first tcp-check
4794 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4795 */
4796 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4797 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4798 goto init;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004799
Christopher Faulet61cc8522020-04-20 14:54:42 +02004800 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
4801 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4802 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4803 ret |= ERR_ALERT | ERR_ABORT;
4804 goto out;
4805 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004806
Christopher Faulet61cc8522020-04-20 14:54:42 +02004807 /* search the first action (connect / send / expect) in the list */
4808 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
4809 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
4810 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4811 "nor tcp_check rule 'connect' with port information.\n",
4812 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4813 ret |= ERR_ALERT | ERR_ABORT;
4814 goto out;
4815 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004816
Christopher Faulet61cc8522020-04-20 14:54:42 +02004817 /* scan the tcp-check ruleset to ensure a port has been configured */
4818 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
4819 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
4820 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4821 "and a tcp_check rule 'connect' with no port information.\n",
4822 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4823 ret |= ERR_ALERT | ERR_ABORT;
4824 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004825 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004826 }
4827
Christopher Faulet61cc8522020-04-20 14:54:42 +02004828 init:
4829 if (!(srv->proxy->options2 & PR_O2_CHK_ANY)) {
4830 struct tcpcheck_ruleset *rs = NULL;
4831 struct tcpcheck_rules *rules = &srv->proxy->tcpcheck_rules;
4832 //char *errmsg = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004833
Christopher Faulet61cc8522020-04-20 14:54:42 +02004834 srv->proxy->options2 &= ~PR_O2_CHK_ANY;
4835 srv->proxy->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulete5870d82020-04-15 11:32:03 +02004836
Christopher Faulet61cc8522020-04-20 14:54:42 +02004837 rs = find_tcpcheck_ruleset("*tcp-check");
4838 if (!rs) {
4839 rs = create_tcpcheck_ruleset("*tcp-check");
4840 if (rs == NULL) {
4841 ha_alert("config: %s '%s': out of memory.\n",
4842 proxy_type_str(srv->proxy), srv->proxy->id);
4843 ret |= ERR_ALERT | ERR_FATAL;
4844 goto out;
4845 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004846 }
4847
Christopher Faulet61cc8522020-04-20 14:54:42 +02004848 free_tcpcheck_vars(&rules->preset_vars);
4849 rules->list = &rs->rules;
4850 rules->flags = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004851 }
4852
Christopher Faulet61cc8522020-04-20 14:54:42 +02004853 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4854 if (err) {
4855 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4856 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4857 ret |= ERR_ALERT | ERR_ABORT;
4858 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004859 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004860 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4861 global.maxsock++;
Christopher Faulete5870d82020-04-15 11:32:03 +02004862
Christopher Faulet61cc8522020-04-20 14:54:42 +02004863 out:
4864 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004865}
4866
Christopher Faulet61cc8522020-04-20 14:54:42 +02004867/* Initializes an agent-check attached to the server <srv>. Non-zero is returned
4868 * if an error occurred.
4869 */
4870static int init_srv_agent_check(struct server *srv)
Christopher Faulete5870d82020-04-15 11:32:03 +02004871{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004872 struct tcpcheck_rule *chk;
4873 const char *err;
4874 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004875
Christopher Faulet61cc8522020-04-20 14:54:42 +02004876 if (!srv->do_agent)
4877 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004878
Christopher Faulet61cc8522020-04-20 14:54:42 +02004879 /* If there is no connect rule preceeding all send / expect rules, an
4880 * implicit one is inserted before all others.
4881 */
4882 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4883 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4884 chk = calloc(1, sizeof(*chk));
4885 if (!chk) {
4886 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4887 " to agent-check for server '%s' (out of memory).\n",
4888 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4889 ret |= ERR_ALERT | ERR_FATAL;
4890 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004891 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004892 chk->action = TCPCHK_ACT_CONNECT;
4893 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4894 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
Christopher Faulete5870d82020-04-15 11:32:03 +02004895 }
4896
Christopher Faulete5870d82020-04-15 11:32:03 +02004897
Christopher Faulet61cc8522020-04-20 14:54:42 +02004898 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
4899 if (err) {
4900 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4901 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4902 ret |= ERR_ALERT | ERR_ABORT;
4903 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004904 }
4905
Christopher Faulet61cc8522020-04-20 14:54:42 +02004906 if (!srv->agent.inter)
4907 srv->agent.inter = srv->check.inter;
4908
4909 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4910 global.maxsock++;
4911
4912 out:
4913 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02004914}
4915
Christopher Faulet61cc8522020-04-20 14:54:42 +02004916/* Check tcp-check health-check configuration for the proxy <px>. */
4917static int check_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02004918{
Christopher Faulet61cc8522020-04-20 14:54:42 +02004919 struct tcpcheck_rule *chk, *back;
4920 char *comment = NULL, *errmsg = NULL;
4921 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
4922 int ret = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02004923
Christopher Faulet61cc8522020-04-20 14:54:42 +02004924 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
4925 deinit_proxy_tcpcheck(px);
4926 goto out;
4927 }
4928
4929 free(px->check_command);
4930 free(px->check_path);
4931 px->check_command = px->check_path = NULL;
4932
4933 if (!px->tcpcheck_rules.list) {
4934 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4935 ret |= ERR_ALERT | ERR_FATAL;
4936 goto out;
4937 }
4938
4939 /* HTTP ruleset only : */
4940 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4941 struct tcpcheck_rule *next;
4942
4943 /* move remaining implicit send rule from "option httpchk" line to the right place.
4944 * If such rule exists, it must be the first one. In this case, the rule is moved
4945 * after the first connect rule, if any. Otherwise, nothing is done.
4946 */
4947 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4948 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4949 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
4950 if (next && next->action == TCPCHK_ACT_CONNECT) {
4951 LIST_DEL(&chk->list);
4952 LIST_ADD(&next->list, &chk->list);
4953 chk->index = next->index;
4954 }
Christopher Faulete5870d82020-04-15 11:32:03 +02004955 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004956
4957 /* add implicit expect rule if the last one is a send. It is inherited from previous
4958 * versions where the http expect rule was optional. Now it is possible to chained
4959 * send/expect rules but the last expect may still be implicit.
4960 */
4961 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
4962 if (chk && chk->action == TCPCHK_ACT_SEND) {
Christopher Faulet8021a5f2020-04-24 13:53:12 +02004963 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
Christopher Faulet61cc8522020-04-20 14:54:42 +02004964 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
4965 px->conf.file, px->conf.line, &errmsg);
4966 if (!next) {
4967 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
4968 "(%s).\n", px->id, errmsg);
4969 free(errmsg);
4970 ret |= ERR_ALERT | ERR_FATAL;
4971 goto out;
4972 }
4973 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
4974 next->index = chk->index;
Christopher Faulete5870d82020-04-15 11:32:03 +02004975 }
4976 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004977
4978 /* For all ruleset: */
4979
4980 /* If there is no connect rule preceeding all send / expect rules, an
4981 * implicit one is inserted before all others.
4982 */
4983 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4984 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4985 chk = calloc(1, sizeof(*chk));
4986 if (!chk) {
4987 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4988 "(out of memory).\n", px->id);
4989 ret |= ERR_ALERT | ERR_FATAL;
4990 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004991 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02004992 chk->action = TCPCHK_ACT_CONNECT;
4993 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4994 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
4995 }
4996
4997 /* Remove all comment rules. To do so, when a such rule is found, the
4998 * comment is assigned to the following rule(s).
4999 */
5000 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
5001 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
5002 free(comment);
5003 comment = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005004 }
5005
Christopher Faulet61cc8522020-04-20 14:54:42 +02005006 prev_action = chk->action;
5007 switch (chk->action) {
5008 case TCPCHK_ACT_COMMENT:
5009 free(comment);
5010 comment = chk->comment;
5011 LIST_DEL(&chk->list);
5012 free(chk);
5013 break;
5014 case TCPCHK_ACT_CONNECT:
5015 if (!chk->comment && comment)
5016 chk->comment = strdup(comment);
5017 /* fall though */
5018 case TCPCHK_ACT_ACTION_KW:
5019 free(comment);
5020 comment = NULL;
5021 break;
5022 case TCPCHK_ACT_SEND:
5023 case TCPCHK_ACT_EXPECT:
5024 if (!chk->comment && comment)
5025 chk->comment = strdup(comment);
5026 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02005027 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005028 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005029 free(comment);
5030 comment = NULL;
5031
5032 out:
5033 return ret;
Christopher Faulete5870d82020-04-15 11:32:03 +02005034}
5035
Christopher Faulet61cc8522020-04-20 14:54:42 +02005036void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Faulete5870d82020-04-15 11:32:03 +02005037{
Christopher Faulet61cc8522020-04-20 14:54:42 +02005038 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
5039 px->tcpcheck_rules.flags = 0;
5040 px->tcpcheck_rules.list = NULL;
5041}
Christopher Faulete5870d82020-04-15 11:32:03 +02005042
Christopher Faulet61cc8522020-04-20 14:54:42 +02005043static void deinit_srv_check(struct server *srv)
5044{
5045 if (srv->check.state & CHK_ST_CONFIGURED)
5046 free_check(&srv->check);
5047 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
5048 srv->do_check = 0;
5049}
Christopher Faulete5870d82020-04-15 11:32:03 +02005050
Christopher Faulet61cc8522020-04-20 14:54:42 +02005051
5052static void deinit_srv_agent_check(struct server *srv)
5053{
5054 if (srv->agent.tcpcheck_rules) {
5055 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
5056 free(srv->agent.tcpcheck_rules);
5057 srv->agent.tcpcheck_rules = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02005058 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005059
Christopher Faulet61cc8522020-04-20 14:54:42 +02005060 if (srv->agent.state & CHK_ST_CONFIGURED)
5061 free_check(&srv->agent);
5062
5063 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
5064 srv->do_agent = 0;
Christopher Faulete5870d82020-04-15 11:32:03 +02005065}
5066
Christopher Faulet61cc8522020-04-20 14:54:42 +02005067static void deinit_tcpchecks()
Christopher Faulete5870d82020-04-15 11:32:03 +02005068{
Christopher Fauletd7cee712020-04-21 13:45:00 +02005069 struct tcpcheck_ruleset *rs;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005070 struct tcpcheck_rule *r, *rb;
Christopher Fauletd7cee712020-04-21 13:45:00 +02005071 struct ebpt_node *node, *next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005072
Christopher Fauletd7cee712020-04-21 13:45:00 +02005073 node = ebpt_first(&shared_tcpchecks);
5074 while (node) {
5075 next = ebpt_next(node);
5076 ebpt_delete(node);
5077 free(node->key);
5078 rs = container_of(node, typeof(*rs), node);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005079 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5080 LIST_DEL(&r->list);
5081 free_tcpcheck(r, 0);
5082 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005083 free(rs);
Christopher Fauletd7cee712020-04-21 13:45:00 +02005084 node = next;
Christopher Faulete5870d82020-04-15 11:32:03 +02005085 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005086}
Christopher Faulete5870d82020-04-15 11:32:03 +02005087
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005088
Christopher Faulet61cc8522020-04-20 14:54:42 +02005089REGISTER_POST_SERVER_CHECK(init_srv_check);
5090REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
5091REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5092REGISTER_POST_CHECK(start_checks);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005093
Christopher Faulet61cc8522020-04-20 14:54:42 +02005094REGISTER_SERVER_DEINIT(deinit_srv_check);
5095REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
5096REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5097REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulete5870d82020-04-15 11:32:03 +02005098
Christopher Faulet61cc8522020-04-20 14:54:42 +02005099/**************************************************************************/
5100/****************************** Email alerts ******************************/
5101/* NOTE: It may be pertinent to use an applet to handle email alerts */
5102/* instead of a tcp-check ruleset */
5103/**************************************************************************/
5104void email_alert_free(struct email_alert *alert)
5105{
5106 struct tcpcheck_rule *rule, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02005107
Christopher Faulet61cc8522020-04-20 14:54:42 +02005108 if (!alert)
5109 return;
5110
5111 if (alert->rules.list) {
5112 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
5113 LIST_DEL(&rule->list);
5114 free_tcpcheck(rule, 1);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005115 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005116 free_tcpcheck_vars(&alert->rules.preset_vars);
5117 free(alert->rules.list);
5118 alert->rules.list = NULL;
5119 }
5120 pool_free(pool_head_email_alert, alert);
5121}
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005122
Christopher Faulet61cc8522020-04-20 14:54:42 +02005123static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
5124{
5125 struct check *check = context;
5126 struct email_alertq *q;
5127 struct email_alert *alert;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005128
Christopher Faulet61cc8522020-04-20 14:54:42 +02005129 q = container_of(check, typeof(*q), check);
5130
5131 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5132 while (1) {
5133 if (!(check->state & CHK_ST_ENABLED)) {
5134 if (LIST_ISEMPTY(&q->email_alerts)) {
5135 /* All alerts processed, queue the task */
5136 t->expire = TICK_ETERNITY;
5137 task_queue(t);
5138 goto end;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005139 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005140
5141 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
5142 LIST_DEL(&alert->list);
5143 t->expire = now_ms;
5144 check->tcpcheck_rules = &alert->rules;
5145 check->status = HCHK_STATUS_INI;
5146 check->state |= CHK_ST_ENABLED;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005147 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005148
5149 process_chk(t, context, state);
5150 if (check->state & CHK_ST_INPROGRESS)
5151 break;
5152
5153 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
5154 email_alert_free(alert);
5155 check->tcpcheck_rules = NULL;
5156 check->server = NULL;
5157 check->state &= ~CHK_ST_ENABLED;
5158 }
5159 end:
5160 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5161 return t;
5162}
5163
5164/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
5165 *
5166 * The function returns 1 in success case, otherwise, it returns 0 and err is
5167 * filled.
5168 */
5169int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
5170{
5171 struct mailer *mailer;
5172 struct email_alertq *queues;
5173 const char *err_str;
5174 int i = 0;
5175
5176 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
5177 memprintf(err, "out of memory while allocating mailer alerts queues");
5178 goto fail_no_queue;
5179 }
5180
5181 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
5182 struct email_alertq *q = &queues[i];
5183 struct check *check = &q->check;
5184 struct task *t;
5185
5186 LIST_INIT(&q->email_alerts);
5187 HA_SPIN_INIT(&q->lock);
5188 check->inter = mls->timeout.mail;
5189 check->rise = DEF_AGENT_RISETIME;
5190 check->proxy = p;
5191 check->fall = DEF_AGENT_FALLTIME;
5192 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
5193 memprintf(err, "%s", err_str);
5194 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005195 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005196
5197 check->xprt = mailer->xprt;
5198 check->addr = mailer->addr;
5199 check->port = get_host_port(&mailer->addr);
5200
5201 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
5202 memprintf(err, "out of memory while allocating mailer alerts task");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005203 goto error;
5204 }
5205
Christopher Faulet61cc8522020-04-20 14:54:42 +02005206 check->task = t;
5207 t->process = process_email_alert;
5208 t->context = check;
5209
5210 /* check this in one ms */
5211 t->expire = TICK_ETERNITY;
5212 check->start = now;
5213 task_queue(t);
5214 }
5215
5216 mls->users++;
5217 free(p->email_alert.mailers.name);
5218 p->email_alert.mailers.m = mls;
5219 p->email_alert.queues = queues;
5220 return 0;
5221
5222 error:
5223 for (i = 0; i < mls->count; i++) {
5224 struct email_alertq *q = &queues[i];
5225 struct check *check = &q->check;
5226
5227 free_check(check);
5228 }
5229 free(queues);
5230 fail_no_queue:
5231 return 1;
5232}
5233
5234static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
5235{
5236 struct tcpcheck_rule *tcpcheck, *prev_check;
5237 struct tcpcheck_expect *expect;
5238
5239 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5240 return 0;
5241 memset(tcpcheck, 0, sizeof(*tcpcheck));
5242 tcpcheck->action = TCPCHK_ACT_EXPECT;
5243
5244 expect = &tcpcheck->expect;
5245 expect->type = TCPCHK_EXPECT_STRING;
5246 LIST_INIT(&expect->onerror_fmt);
5247 LIST_INIT(&expect->onsuccess_fmt);
5248 expect->ok_status = HCHK_STATUS_L7OKD;
5249 expect->err_status = HCHK_STATUS_L7RSP;
5250 expect->tout_status = HCHK_STATUS_L7TOUT;
5251 expect->data = ist2(strdup(str), strlen(str));
Christopher Fauletb61caf42020-04-21 10:57:42 +02005252 if (!isttest(expect->data)) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005253 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5254 return 0;
5255 }
5256
5257 /* All tcp-check expect points back to the first inverse expect rule
5258 * in a chain of one or more expect rule, potentially itself.
5259 */
5260 tcpcheck->expect.head = tcpcheck;
5261 list_for_each_entry_rev(prev_check, rules->list, list) {
5262 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5263 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
5264 tcpcheck->expect.head = prev_check;
5265 continue;
5266 }
5267 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
5268 break;
5269 }
5270 LIST_ADDQ(rules->list, &tcpcheck->list);
5271 return 1;
5272}
5273
5274static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
5275{
5276 struct tcpcheck_rule *tcpcheck;
5277 struct tcpcheck_send *send;
5278 const char *in;
5279 char *dst;
5280 int i;
5281
5282 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5283 return 0;
5284 memset(tcpcheck, 0, sizeof(*tcpcheck));
5285 tcpcheck->action = TCPCHK_ACT_SEND;
5286
5287 send = &tcpcheck->send;
5288 send->type = TCPCHK_SEND_STRING;
5289
5290 for (i = 0; strs[i]; i++)
5291 send->data.len += strlen(strs[i]);
5292
Christopher Fauletb61caf42020-04-21 10:57:42 +02005293 send->data.ptr = malloc(istlen(send->data) + 1);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005294 if (!isttest(send->data)) {
5295 pool_free(pool_head_tcpcheck_rule, tcpcheck);
5296 return 0;
5297 }
5298
Christopher Fauletb61caf42020-04-21 10:57:42 +02005299 dst = istptr(send->data);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005300 for (i = 0; strs[i]; i++)
5301 for (in = strs[i]; (*dst = *in++); dst++);
5302 *dst = 0;
5303
5304 LIST_ADDQ(rules->list, &tcpcheck->list);
5305 return 1;
5306}
5307
5308static int enqueue_one_email_alert(struct proxy *p, struct server *s,
5309 struct email_alertq *q, const char *msg)
5310{
5311 struct email_alert *alert;
5312 struct tcpcheck_rule *tcpcheck;
5313 struct check *check = &q->check;
5314
5315 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
5316 goto error;
5317 LIST_INIT(&alert->list);
5318 alert->rules.flags = TCPCHK_RULES_TCP_CHK;
5319 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
5320 if (!alert->rules.list)
5321 goto error;
5322 LIST_INIT(alert->rules.list);
5323 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
5324 alert->srv = s;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005325
Christopher Faulet61cc8522020-04-20 14:54:42 +02005326 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
5327 goto error;
5328 memset(tcpcheck, 0, sizeof(*tcpcheck));
5329 tcpcheck->action = TCPCHK_ACT_CONNECT;
5330 tcpcheck->comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005331
Christopher Faulet61cc8522020-04-20 14:54:42 +02005332 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005333
Christopher Faulet61cc8522020-04-20 14:54:42 +02005334 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005335 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005336
5337 {
5338 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
5339 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5340 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005341 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005342
Christopher Faulet61cc8522020-04-20 14:54:42 +02005343 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5344 goto error;
5345
5346 {
5347 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
5348 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005349 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005350 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005351
5352 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5353 goto error;
5354
5355 {
5356 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
5357 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005358 goto error;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005359 }
5360
Christopher Faulet61cc8522020-04-20 14:54:42 +02005361 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
5362 goto error;
5363
5364 {
5365 const char * const strs[2] = { "DATA\r\n" };
5366 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005367 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005368 }
5369
5370 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
5371 goto error;
5372
5373 {
5374 struct tm tm;
5375 char datestr[48];
5376 const char * const strs[18] = {
5377 "From: ", p->email_alert.from, "\r\n",
5378 "To: ", p->email_alert.to, "\r\n",
5379 "Date: ", datestr, "\r\n",
5380 "Subject: [HAproxy Alert] ", msg, "\r\n",
5381 "\r\n",
5382 msg, "\r\n",
5383 "\r\n",
5384 ".\r\n",
5385 NULL
5386 };
5387
5388 get_localtime(date.tv_sec, &tm);
5389
5390 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005391 goto error;
5392 }
Christopher Faulet61cc8522020-04-20 14:54:42 +02005393
5394 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005395 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005396 }
5397
5398 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005399 goto error;
Christopher Faulet61cc8522020-04-20 14:54:42 +02005400
5401 {
5402 const char * const strs[2] = { "QUIT\r\n" };
5403 if (!add_tcpcheck_send_strs(&alert->rules, strs))
5404 goto error;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005405 }
5406
Christopher Faulet61cc8522020-04-20 14:54:42 +02005407 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
5408 goto error;
5409
5410 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
5411 task_wakeup(check->task, TASK_WOKEN_MSG);
5412 LIST_ADDQ(&q->email_alerts, &alert->list);
5413 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
5414 return 1;
5415
5416error:
5417 email_alert_free(alert);
5418 return 0;
5419}
5420
5421static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
5422{
5423 int i;
5424 struct mailer *mailer;
5425
5426 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
5427 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
5428 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
5429 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
5430 return;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005431 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005432 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005433
Christopher Faulet61cc8522020-04-20 14:54:42 +02005434 return;
5435}
5436
5437/*
5438 * Send email alert if configured.
5439 */
5440void send_email_alert(struct server *s, int level, const char *format, ...)
5441{
5442 va_list argp;
5443 char buf[1024];
5444 int len;
5445 struct proxy *p = s->proxy;
5446
5447 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
5448 return;
5449
5450 va_start(argp, format);
5451 len = vsnprintf(buf, sizeof(buf), format, argp);
5452 va_end(argp);
5453
5454 if (len < 0 || len >= sizeof(buf)) {
5455 ha_alert("Email alert [%s] could not format message\n", p->id);
5456 return;
5457 }
5458
5459 enqueue_email_alert(p, s, buf);
5460}
5461
5462/**************************************************************************/
5463/************************** Check sample fetches **************************/
5464/**************************************************************************/
5465/* extracts check payload at a fixed position and length */
5466static int
5467smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
5468{
5469 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
5470 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
5471 struct check *check = (smp->sess ? objt_check(smp->sess->origin) : NULL);
5472 struct buffer *buf;
5473
5474 if (!check)
5475 return 0;
5476
5477 buf = &check->bi;
5478 if (buf_offset > b_data(buf))
5479 goto no_match;
5480 if (buf_offset + buf_size > b_data(buf))
5481 buf_size = 0;
5482
5483 /* init chunk as read only */
5484 smp->data.type = SMP_T_STR;
5485 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
5486 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
5487
5488 return 1;
5489
5490 no_match:
5491 smp->flags = 0;
5492 return 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005493}
5494
Christopher Faulet61cc8522020-04-20 14:54:42 +02005495static struct sample_fetch_kw_list smp_kws = {ILH, {
5496 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
5497 { /* END */ },
5498}};
5499
5500INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
5501
5502
5503/**************************************************************************/
5504/************************ Check's parsing functions ***********************/
5505/**************************************************************************/
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005506/* Parses the "tcp-check" proxy keyword */
5507static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5508 struct proxy *defpx, const char *file, int line,
5509 char **errmsg)
5510{
Christopher Faulet404f9192020-04-09 23:13:54 +02005511 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005512 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005513 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005514
5515 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5516 ret = 1;
5517
Christopher Faulet404f9192020-04-09 23:13:54 +02005518 /* Deduce the ruleset name from the proxy info */
5519 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5520 ((curpx == defpx) ? "defaults" : curpx->id),
5521 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005522
Christopher Faulet61cc8522020-04-20 14:54:42 +02005523 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005524 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005525 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005526 if (rs == NULL) {
5527 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005528 goto error;
5529 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005530 }
5531
Gaetan Rivet5301b012020-02-25 17:19:17 +01005532 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005533 if (!LIST_ISEMPTY(&rs->rules)) {
5534 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005535 index = chk->index + 1;
5536 }
5537
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005538 cur_arg = 1;
5539 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005540 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005541 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005542 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005543 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005544 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005545 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005546 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005547 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005548 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5549
5550 if (!kw) {
5551 action_kw_tcp_check_build_list(&trash);
5552 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5553 "%s%s. but got '%s'",
5554 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5555 goto error;
5556 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005557 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005558 }
5559
5560 if (!chk) {
5561 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5562 goto error;
5563 }
5564 ret = (*errmsg != NULL); /* Handle warning */
5565
5566 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005567 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005568 LIST_ADDQ(&rs->rules, &chk->list);
5569
5570 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
Christopher Fauletd7e63962020-04-17 20:15:59 +02005571 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005572 /* Use this ruleset if the proxy already has tcp-check enabled */
5573 curpx->tcpcheck_rules.list = &rs->rules;
5574 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5575 }
5576 else {
5577 /* mark this ruleset as unused for now */
5578 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5579 }
5580
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005581 return ret;
5582
5583 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005584 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005585 free_tcpcheck_ruleset(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005586 return -1;
5587}
5588
Christopher Faulet51b129f2020-04-09 15:54:18 +02005589/* Parses the "http-check" proxy keyword */
5590static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5591 struct proxy *defpx, const char *file, int line,
5592 char **errmsg)
5593{
Christopher Faulete5870d82020-04-15 11:32:03 +02005594 struct tcpcheck_ruleset *rs = NULL;
5595 struct tcpcheck_rule *chk = NULL;
5596 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005597
5598 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5599 ret = 1;
5600
5601 cur_arg = 1;
5602 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5603 /* enable a graceful server shutdown on an HTTP 404 response */
5604 curpx->options |= PR_O_DISABLE404;
5605 if (too_many_args(1, args, errmsg, NULL))
5606 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005607 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005608 }
5609 else if (strcmp(args[cur_arg], "send-state") == 0) {
5610 /* enable emission of the apparent state of a server in HTTP checks */
5611 curpx->options2 |= PR_O2_CHK_SNDST;
5612 if (too_many_args(1, args, errmsg, NULL))
5613 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005614 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005615 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005616
Christopher Faulete5870d82020-04-15 11:32:03 +02005617 /* Deduce the ruleset name from the proxy info */
5618 chunk_printf(&trash, "*http-check-%s_%s-%d",
5619 ((curpx == defpx) ? "defaults" : curpx->id),
5620 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005621
Christopher Faulet61cc8522020-04-20 14:54:42 +02005622 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005623 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005624 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02005625 if (rs == NULL) {
5626 memprintf(errmsg, "out of memory.\n");
5627 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005628 }
5629 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005630
Christopher Faulete5870d82020-04-15 11:32:03 +02005631 index = 0;
5632 if (!LIST_ISEMPTY(&rs->rules)) {
5633 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5634 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5635 index = chk->index + 1;
5636 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005637
Christopher Faulete5870d82020-04-15 11:32:03 +02005638 if (strcmp(args[cur_arg], "connect") == 0)
5639 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5640 else if (strcmp(args[cur_arg], "send") == 0)
5641 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5642 else if (strcmp(args[cur_arg], "expect") == 0)
5643 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5644 file, line, errmsg);
5645 else if (strcmp(args[cur_arg], "comment") == 0)
5646 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5647 else {
5648 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005649
Christopher Faulete5870d82020-04-15 11:32:03 +02005650 if (!kw) {
5651 action_kw_tcp_check_build_list(&trash);
5652 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5653 " 'send', 'expect'%s%s. but got '%s'",
5654 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5655 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005656 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005657 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5658 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005659
Christopher Faulete5870d82020-04-15 11:32:03 +02005660 if (!chk) {
5661 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5662 goto error;
5663 }
5664 ret = (*errmsg != NULL); /* Handle warning */
5665
5666 chk->index = index;
5667 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5668 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5669 /* Use this ruleset if the proxy already has http-check enabled */
5670 curpx->tcpcheck_rules.list = &rs->rules;
5671 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5672 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5673 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5674 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005675 goto error;
5676 }
5677 }
5678 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005679 /* mark this ruleset as unused for now */
5680 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5681 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005682 }
5683
Christopher Faulete5870d82020-04-15 11:32:03 +02005684 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005685 return ret;
5686
5687 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005688 free_tcpcheck(chk, 0);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005689 free_tcpcheck_ruleset(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005690 return -1;
5691}
5692
Christopher Faulete9111b62020-04-09 18:12:08 +02005693/* Parses the "external-check" proxy keyword */
5694static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5695 struct proxy *defpx, const char *file, int line,
5696 char **errmsg)
5697{
5698 int cur_arg, ret = 0;
5699
5700 cur_arg = 1;
5701 if (!*(args[cur_arg])) {
5702 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5703 goto error;
5704 }
5705
5706 if (strcmp(args[cur_arg], "command") == 0) {
5707 if (too_many_args(2, args, errmsg, NULL))
5708 goto error;
5709 if (!*(args[cur_arg+1])) {
5710 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5711 goto error;
5712 }
5713 free(curpx->check_command);
5714 curpx->check_command = strdup(args[cur_arg+1]);
5715 }
5716 else if (strcmp(args[cur_arg], "path") == 0) {
5717 if (too_many_args(2, args, errmsg, NULL))
5718 goto error;
5719 if (!*(args[cur_arg+1])) {
5720 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5721 goto error;
5722 }
5723 free(curpx->check_path);
5724 curpx->check_path = strdup(args[cur_arg+1]);
5725 }
5726 else {
5727 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5728 args[0], args[1]);
5729 goto error;
5730 }
5731
5732 ret = (*errmsg != NULL); /* Handle warning */
5733 return ret;
5734
5735error:
5736 return -1;
5737}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005738
Christopher Faulet430e4802020-04-09 15:28:16 +02005739/* Parses the "option tcp-check" proxy keyword */
5740int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5741 const char *file, int line)
5742{
Christopher Faulet404f9192020-04-09 23:13:54 +02005743 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005744 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5745 int err_code = 0;
5746
5747 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5748 err_code |= ERR_WARN;
5749
5750 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5751 goto out;
5752
Christopher Faulet404f9192020-04-09 23:13:54 +02005753 curpx->options2 &= ~PR_O2_CHK_ANY;
5754 curpx->options2 |= PR_O2_TCPCHK_CHK;
5755
Christopher Fauletd7e63962020-04-17 20:15:59 +02005756 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
Christopher Faulet404f9192020-04-09 23:13:54 +02005757 /* If a tcp-check rulesset is already set, do nothing */
5758 if (rules->list)
5759 goto out;
5760
5761 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5762 * get it.
5763 */
5764 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5765 goto curpx_ruleset;
5766
5767 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5768 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
Christopher Faulet61cc8522020-04-20 14:54:42 +02005769 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005770 if (rs)
5771 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005772 }
5773
Christopher Faulet404f9192020-04-09 23:13:54 +02005774 curpx_ruleset:
5775 /* Deduce the ruleset name from the proxy info */
5776 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5777 ((curpx == defpx) ? "defaults" : curpx->id),
5778 curpx->conf.file, curpx->conf.line);
5779
Christopher Faulet61cc8522020-04-20 14:54:42 +02005780 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005781 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02005782 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulet404f9192020-04-09 23:13:54 +02005783 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005784 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5785 goto error;
5786 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005787 }
5788
Christopher Faulet404f9192020-04-09 23:13:54 +02005789 ruleset_found:
5790 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet404f9192020-04-09 23:13:54 +02005791 rules->list = &rs->rules;
Christopher Fauletd7e63962020-04-17 20:15:59 +02005792 rules->flags |= TCPCHK_RULES_TCP_CHK;
Christopher Faulet430e4802020-04-09 15:28:16 +02005793
5794 out:
5795 return err_code;
5796
5797 error:
5798 err_code |= ERR_ALERT | ERR_FATAL;
5799 goto out;
5800}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005801
5802/* Parses the "option redis-check" proxy keyword */
5803int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5804 const char *file, int line)
5805{
5806 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5807 static char *redis_res = "+PONG\r\n";
5808
5809 struct tcpcheck_ruleset *rs = NULL;
5810 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5811 struct tcpcheck_rule *chk;
5812 char *errmsg = NULL;
5813 int err_code = 0;
5814
5815 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5816 err_code |= ERR_WARN;
5817
5818 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5819 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005820
5821 curpx->options2 &= ~PR_O2_CHK_ANY;
5822 curpx->options2 |= PR_O2_TCPCHK_CHK;
5823
5824 free_tcpcheck_vars(&rules->preset_vars);
5825 rules->list = NULL;
5826 rules->flags = 0;
5827
Christopher Faulet61cc8522020-04-20 14:54:42 +02005828 rs = find_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005829 if (rs)
5830 goto ruleset_found;
5831
Christopher Faulet61cc8522020-04-20 14:54:42 +02005832 rs = create_tcpcheck_ruleset("*redis-check");
Christopher Faulet33f05df2020-04-01 11:08:50 +02005833 if (rs == NULL) {
5834 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5835 goto error;
5836 }
5837
5838 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5839 1, curpx, &rs->rules, file, line, &errmsg);
5840 if (!chk) {
5841 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5842 goto error;
5843 }
5844 chk->index = 0;
5845 LIST_ADDQ(&rs->rules, &chk->list);
5846
5847 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5848 "error-status", "L7STS",
5849 "on-error", "%[check.payload(),cut_crlf]",
5850 "on-success", "Redis server is ok",
5851 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005852 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005853 if (!chk) {
5854 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5855 goto error;
5856 }
5857 chk->index = 1;
5858 LIST_ADDQ(&rs->rules, &chk->list);
5859
Christopher Fauletd7cee712020-04-21 13:45:00 +02005860 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005861
5862 ruleset_found:
5863 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005864 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005865
5866 out:
5867 free(errmsg);
5868 return err_code;
5869
5870 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005871 free_tcpcheck_ruleset(rs);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005872 err_code |= ERR_ALERT | ERR_FATAL;
5873 goto out;
5874}
5875
Christopher Faulet811f78c2020-04-01 11:10:27 +02005876
5877/* Parses the "option ssl-hello-chk" proxy keyword */
5878int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5879 const char *file, int line)
5880{
5881 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5882 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5883 *
5884 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5885 */
5886 static char sslv3_client_hello[] = {
5887 "16" /* ContentType : 0x16 = Hanshake */
5888 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5889 "0079" /* ContentLength : 0x79 bytes after this one */
5890 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5891 "000075" /* HandshakeLength : 0x75 bytes after this one */
5892 "0300" /* Hello Version : 0x0300 = v3 */
5893 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5894 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5895 "00" /* Session ID length : empty (no session ID) */
5896 "004E" /* Cipher Suite Length : 78 bytes after this one */
5897 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5898 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5899 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5900 "000D" "000E" "000F" "0010" /* various bit lengths, */
5901 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5902 "0015" "0016" "0017" "0018"
5903 "0019" "001A" "001B" "002F"
5904 "0030" "0031" "0032" "0033"
5905 "0034" "0035" "0036" "0037"
5906 "0038" "0039" "003A"
5907 "01" /* Compression Length : 0x01 = 1 byte for types */
5908 "00" /* Compression Type : 0x00 = NULL compression */
5909 };
5910
5911 struct tcpcheck_ruleset *rs = NULL;
5912 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5913 struct tcpcheck_rule *chk;
5914 char *errmsg = NULL;
5915 int err_code = 0;
5916
5917 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5918 err_code |= ERR_WARN;
5919
5920 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5921 goto out;
5922
Christopher Faulet811f78c2020-04-01 11:10:27 +02005923 curpx->options2 &= ~PR_O2_CHK_ANY;
5924 curpx->options2 |= PR_O2_TCPCHK_CHK;
5925
5926 free_tcpcheck_vars(&rules->preset_vars);
5927 rules->list = NULL;
5928 rules->flags = 0;
5929
Christopher Faulet61cc8522020-04-20 14:54:42 +02005930 rs = find_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005931 if (rs)
5932 goto ruleset_found;
5933
Christopher Faulet61cc8522020-04-20 14:54:42 +02005934 rs = create_tcpcheck_ruleset("*ssl-hello-check");
Christopher Faulet811f78c2020-04-01 11:10:27 +02005935 if (rs == NULL) {
5936 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5937 goto error;
5938 }
5939
5940 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5941 1, curpx, &rs->rules, file, line, &errmsg);
5942 if (!chk) {
5943 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5944 goto error;
5945 }
5946 chk->index = 0;
5947 LIST_ADDQ(&rs->rules, &chk->list);
5948
5949 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005950 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005951 "error-status", "L6RSP", "tout-status", "L6TOUT",
5952 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005953 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005954 if (!chk) {
5955 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5956 goto error;
5957 }
5958 chk->index = 1;
5959 LIST_ADDQ(&rs->rules, &chk->list);
5960
Christopher Fauletd7cee712020-04-21 13:45:00 +02005961 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005962
5963 ruleset_found:
5964 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005965 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005966
5967 out:
5968 free(errmsg);
5969 return err_code;
5970
5971 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02005972 free_tcpcheck_ruleset(rs);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005973 err_code |= ERR_ALERT | ERR_FATAL;
5974 goto out;
5975}
5976
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005977/* Parses the "option smtpchk" proxy keyword */
5978int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5979 const char *file, int line)
5980{
5981 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5982
5983 struct tcpcheck_ruleset *rs = NULL;
5984 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5985 struct tcpcheck_rule *chk;
5986 struct tcpcheck_var *var = NULL;
5987 char *cmd = NULL, *errmsg = NULL;
5988 int err_code = 0;
5989
5990 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5991 err_code |= ERR_WARN;
5992
5993 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5994 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005995
5996 curpx->options2 &= ~PR_O2_CHK_ANY;
5997 curpx->options2 |= PR_O2_TCPCHK_CHK;
5998
5999 free_tcpcheck_vars(&rules->preset_vars);
6000 rules->list = NULL;
6001 rules->flags = 0;
6002
6003 cur_arg += 2;
6004 if (*args[cur_arg] && *args[cur_arg+1] &&
6005 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
6006 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
6007 if (cmd)
6008 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
6009 }
6010 else {
6011 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
6012 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
6013 cmd = strdup("HELO localhost");
6014 }
6015
Christopher Fauletb61caf42020-04-21 10:57:42 +02006016 var = create_tcpcheck_var(ist("check.smtp_cmd"));
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006017 if (cmd == NULL || var == NULL) {
6018 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6019 goto error;
6020 }
6021 var->data.type = SMP_T_STR;
6022 var->data.u.str.area = cmd;
6023 var->data.u.str.data = strlen(cmd);
6024 LIST_INIT(&var->list);
6025 LIST_ADDQ(&rules->preset_vars, &var->list);
6026 cmd = NULL;
6027 var = NULL;
6028
Christopher Faulet61cc8522020-04-20 14:54:42 +02006029 rs = find_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006030 if (rs)
6031 goto ruleset_found;
6032
Christopher Faulet61cc8522020-04-20 14:54:42 +02006033 rs = create_tcpcheck_ruleset("*smtp-check");
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006034 if (rs == NULL) {
6035 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6036 goto error;
6037 }
6038
6039 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6040 1, curpx, &rs->rules, file, line, &errmsg);
6041 if (!chk) {
6042 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6043 goto error;
6044 }
6045 chk->index = 0;
6046 LIST_ADDQ(&rs->rules, &chk->list);
6047
6048 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
6049 "min-recv", "4",
6050 "error-status", "L7RSP",
6051 "on-error", "%[check.payload(),cut_crlf]",
6052 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006053 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006054 if (!chk) {
6055 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6056 goto error;
6057 }
6058 chk->index = 1;
6059 LIST_ADDQ(&rs->rules, &chk->list);
6060
6061 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6062 "min-recv", "4",
6063 "error-status", "L7STS",
6064 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6065 "status-code", "check.payload(0,3)",
6066 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006067 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006068 if (!chk) {
6069 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6070 goto error;
6071 }
6072 chk->index = 2;
6073 LIST_ADDQ(&rs->rules, &chk->list);
6074
6075 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6076 1, curpx, &rs->rules, file, line, &errmsg);
6077 if (!chk) {
6078 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6079 goto error;
6080 }
6081 chk->index = 3;
6082 LIST_ADDQ(&rs->rules, &chk->list);
6083
6084 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6085 "min-recv", "4",
6086 "error-status", "L7STS",
6087 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6088 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6089 "status-code", "check.payload(0,3)",
6090 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006091 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006092 if (!chk) {
6093 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6094 goto error;
6095 }
6096 chk->index = 4;
6097 LIST_ADDQ(&rs->rules, &chk->list);
6098
Christopher Fauletd7cee712020-04-21 13:45:00 +02006099 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006100
6101 ruleset_found:
6102 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006103 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006104
6105 out:
6106 free(errmsg);
6107 return err_code;
6108
6109 error:
6110 free(cmd);
6111 free(var);
6112 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006113 free_tcpcheck_ruleset(rs);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006114 err_code |= ERR_ALERT | ERR_FATAL;
6115 goto out;
6116}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006117
Christopher Fauletce355072020-04-02 11:44:39 +02006118/* Parses the "option pgsql-check" proxy keyword */
6119int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6120 const char *file, int line)
6121{
6122 static char pgsql_req[] = {
6123 "%[var(check.plen),htonl,hex]" /* The packet length*/
6124 "00030000" /* the version 3.0 */
6125 "7573657200" /* "user" key */
6126 "%[var(check.username),hex]00" /* the username */
6127 "00"
6128 };
6129
6130 struct tcpcheck_ruleset *rs = NULL;
6131 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6132 struct tcpcheck_rule *chk;
6133 struct tcpcheck_var *var = NULL;
6134 char *user = NULL, *errmsg = NULL;
6135 size_t packetlen = 0;
6136 int err_code = 0;
6137
6138 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6139 err_code |= ERR_WARN;
6140
6141 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6142 goto out;
6143
Christopher Fauletce355072020-04-02 11:44:39 +02006144 curpx->options2 &= ~PR_O2_CHK_ANY;
6145 curpx->options2 |= PR_O2_TCPCHK_CHK;
6146
6147 free_tcpcheck_vars(&rules->preset_vars);
6148 rules->list = NULL;
6149 rules->flags = 0;
6150
6151 cur_arg += 2;
6152 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6153 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6154 file, line, args[0], args[1]);
6155 goto error;
6156 }
6157 if (strcmp(args[cur_arg], "user") == 0) {
6158 packetlen = 15 + strlen(args[cur_arg+1]);
6159 user = strdup(args[cur_arg+1]);
6160
Christopher Fauletb61caf42020-04-21 10:57:42 +02006161 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletce355072020-04-02 11:44:39 +02006162 if (user == NULL || var == NULL) {
6163 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6164 goto error;
6165 }
6166 var->data.type = SMP_T_STR;
6167 var->data.u.str.area = user;
6168 var->data.u.str.data = strlen(user);
6169 LIST_INIT(&var->list);
6170 LIST_ADDQ(&rules->preset_vars, &var->list);
6171 user = NULL;
6172 var = NULL;
6173
Christopher Fauletb61caf42020-04-21 10:57:42 +02006174 var = create_tcpcheck_var(ist("check.plen"));
Christopher Fauletce355072020-04-02 11:44:39 +02006175 if (var == NULL) {
6176 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6177 goto error;
6178 }
6179 var->data.type = SMP_T_SINT;
6180 var->data.u.sint = packetlen;
6181 LIST_INIT(&var->list);
6182 LIST_ADDQ(&rules->preset_vars, &var->list);
6183 var = NULL;
6184 }
6185 else {
6186 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6187 file, line, args[0], args[1]);
6188 goto error;
6189 }
6190
Christopher Faulet61cc8522020-04-20 14:54:42 +02006191 rs = find_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006192 if (rs)
6193 goto ruleset_found;
6194
Christopher Faulet61cc8522020-04-20 14:54:42 +02006195 rs = create_tcpcheck_ruleset("*pgsql-check");
Christopher Fauletce355072020-04-02 11:44:39 +02006196 if (rs == NULL) {
6197 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6198 goto error;
6199 }
6200
6201 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6202 1, curpx, &rs->rules, file, line, &errmsg);
6203 if (!chk) {
6204 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6205 goto error;
6206 }
6207 chk->index = 0;
6208 LIST_ADDQ(&rs->rules, &chk->list);
6209
6210 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
6211 1, curpx, &rs->rules, file, line, &errmsg);
6212 if (!chk) {
6213 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6214 goto error;
6215 }
6216 chk->index = 1;
6217 LIST_ADDQ(&rs->rules, &chk->list);
6218
6219 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6220 "min-recv", "5",
6221 "error-status", "L7RSP",
6222 "on-error", "%[check.payload(6,0)]",
6223 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006224 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006225 if (!chk) {
6226 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6227 goto error;
6228 }
6229 chk->index = 2;
6230 LIST_ADDQ(&rs->rules, &chk->list);
6231
6232 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
6233 "min-recv", "9",
6234 "error-status", "L7STS",
6235 "on-success", "PostgreSQL server is ok",
6236 "on-error", "PostgreSQL unknown error",
6237 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006238 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006239 if (!chk) {
6240 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6241 goto error;
6242 }
6243 chk->index = 3;
6244 LIST_ADDQ(&rs->rules, &chk->list);
6245
Christopher Fauletd7cee712020-04-21 13:45:00 +02006246 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletce355072020-04-02 11:44:39 +02006247
6248 ruleset_found:
6249 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006250 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006251
6252 out:
6253 free(errmsg);
6254 return err_code;
6255
6256 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006257 free(user);
6258 free(var);
6259 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006260 free_tcpcheck_ruleset(rs);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006261 err_code |= ERR_ALERT | ERR_FATAL;
6262 goto out;
6263}
6264
6265
6266/* Parses the "option mysql-check" proxy keyword */
6267int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6268 const char *file, int line)
6269{
6270 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6271 * const char mysql40_client_auth_pkt[] = {
6272 * "\x0e\x00\x00" // packet length
6273 * "\x01" // packet number
6274 * "\x00\x00" // client capabilities
6275 * "\x00\x00\x01" // max packet
6276 * "haproxy\x00" // username (null terminated string)
6277 * "\x00" // filler (always 0x00)
6278 * "\x01\x00\x00" // packet length
6279 * "\x00" // packet number
6280 * "\x01" // COM_QUIT command
6281 * };
6282 */
6283 static char mysql40_rsname[] = "*mysql40-check";
6284 static char mysql40_req[] = {
6285 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6286 "0080" /* client capabilities */
6287 "000001" /* max packet */
6288 "%[var(check.username),hex]00" /* the username */
6289 "00" /* filler (always 0x00) */
6290 "010000" /* packet length*/
6291 "00" /* sequence ID */
6292 "01" /* COM_QUIT command */
6293 };
6294
6295 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6296 * const char mysql41_client_auth_pkt[] = {
6297 * "\x0e\x00\x00\" // packet length
6298 * "\x01" // packet number
6299 * "\x00\x00\x00\x00" // client capabilities
6300 * "\x00\x00\x00\x01" // max packet
6301 * "\x21" // character set (UTF-8)
6302 * char[23] // All zeroes
6303 * "haproxy\x00" // username (null terminated string)
6304 * "\x00" // filler (always 0x00)
6305 * "\x01\x00\x00" // packet length
6306 * "\x00" // packet number
6307 * "\x01" // COM_QUIT command
6308 * };
6309 */
6310 static char mysql41_rsname[] = "*mysql41-check";
6311 static char mysql41_req[] = {
6312 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6313 "00820000" /* client capabilities */
6314 "00800001" /* max packet */
6315 "21" /* character set (UTF-8) */
6316 "000000000000000000000000" /* 23 bytes, al zeroes */
6317 "0000000000000000000000"
6318 "%[var(check.username),hex]00" /* the username */
6319 "00" /* filler (always 0x00) */
6320 "010000" /* packet length*/
6321 "00" /* sequence ID */
6322 "01" /* COM_QUIT command */
6323 };
6324
6325 struct tcpcheck_ruleset *rs = NULL;
6326 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6327 struct tcpcheck_rule *chk;
6328 struct tcpcheck_var *var = NULL;
6329 char *mysql_rsname = "*mysql-check";
6330 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6331 int index = 0, err_code = 0;
6332
6333 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6334 err_code |= ERR_WARN;
6335
6336 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6337 goto out;
6338
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006339 curpx->options2 &= ~PR_O2_CHK_ANY;
6340 curpx->options2 |= PR_O2_TCPCHK_CHK;
6341
6342 free_tcpcheck_vars(&rules->preset_vars);
6343 rules->list = NULL;
6344 rules->flags = 0;
6345
6346 cur_arg += 2;
6347 if (*args[cur_arg]) {
6348 char *user;
6349 int packetlen, userlen;
6350
6351 if (strcmp(args[cur_arg], "user") != 0) {
6352 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6353 file, line, args[0], args[1], args[cur_arg]);
6354 goto error;
6355 }
6356
6357 if (*(args[cur_arg+1]) == 0) {
6358 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6359 file, line, args[0], args[1], args[cur_arg]);
6360 goto error;
6361 }
6362
6363 hdr = calloc(4, sizeof(*hdr));
6364 user = strdup(args[cur_arg+1]);
6365 userlen = strlen(args[cur_arg+1]);
6366
6367 if (hdr == NULL || user == NULL) {
6368 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6369 goto error;
6370 }
6371
6372 if (*args[cur_arg+2]) {
6373 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6374 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6375 file, line, args[cur_arg], args[cur_arg+2]);
6376 goto error;
6377 }
6378 packetlen = userlen + 7 + 27;
6379 mysql_req = mysql41_req;
6380 mysql_rsname = mysql41_rsname;
6381 }
6382 else {
6383 packetlen = userlen + 7;
6384 mysql_req = mysql40_req;
6385 mysql_rsname = mysql40_rsname;
6386 }
6387
6388 hdr[0] = (unsigned char)(packetlen & 0xff);
6389 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6390 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6391 hdr[3] = 1;
6392
Christopher Fauletb61caf42020-04-21 10:57:42 +02006393 var = create_tcpcheck_var(ist("check.header"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006394 if (var == NULL) {
6395 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6396 goto error;
6397 }
6398 var->data.type = SMP_T_STR;
6399 var->data.u.str.area = hdr;
6400 var->data.u.str.data = 4;
6401 LIST_INIT(&var->list);
6402 LIST_ADDQ(&rules->preset_vars, &var->list);
6403 hdr = NULL;
6404 var = NULL;
6405
Christopher Fauletb61caf42020-04-21 10:57:42 +02006406 var = create_tcpcheck_var(ist("check.username"));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006407 if (var == NULL) {
6408 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6409 goto error;
6410 }
6411 var->data.type = SMP_T_STR;
6412 var->data.u.str.area = user;
6413 var->data.u.str.data = strlen(user);
6414 LIST_INIT(&var->list);
6415 LIST_ADDQ(&rules->preset_vars, &var->list);
6416 user = NULL;
6417 var = NULL;
6418 }
6419
Christopher Faulet61cc8522020-04-20 14:54:42 +02006420 rs = find_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006421 if (rs)
6422 goto ruleset_found;
6423
Christopher Faulet61cc8522020-04-20 14:54:42 +02006424 rs = create_tcpcheck_ruleset(mysql_rsname);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006425 if (rs == NULL) {
6426 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6427 goto error;
6428 }
6429
6430 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6431 1, curpx, &rs->rules, file, line, &errmsg);
6432 if (!chk) {
6433 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6434 goto error;
6435 }
6436 chk->index = index++;
6437 LIST_ADDQ(&rs->rules, &chk->list);
6438
6439 if (mysql_req) {
6440 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6441 1, curpx, &rs->rules, file, line, &errmsg);
6442 if (!chk) {
6443 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6444 goto error;
6445 }
6446 chk->index = index++;
6447 LIST_ADDQ(&rs->rules, &chk->list);
6448 }
6449
6450 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006451 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006452 if (!chk) {
6453 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6454 goto error;
6455 }
6456 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6457 chk->index = index++;
6458 LIST_ADDQ(&rs->rules, &chk->list);
6459
6460 if (mysql_req) {
6461 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006462 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006463 if (!chk) {
6464 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6465 goto error;
6466 }
6467 chk->expect.custom = tcpcheck_mysql_expect_ok;
6468 chk->index = index++;
6469 LIST_ADDQ(&rs->rules, &chk->list);
6470 }
6471
Christopher Fauletd7cee712020-04-21 13:45:00 +02006472 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006473
6474 ruleset_found:
6475 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006476 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006477
6478 out:
6479 free(errmsg);
6480 return err_code;
6481
6482 error:
6483 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006484 free(user);
6485 free(var);
6486 free_tcpcheck_vars(&rules->preset_vars);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006487 free_tcpcheck_ruleset(rs);
Christopher Fauletce355072020-04-02 11:44:39 +02006488 err_code |= ERR_ALERT | ERR_FATAL;
6489 goto out;
6490}
6491
Christopher Faulet1997eca2020-04-03 23:13:50 +02006492int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6493 const char *file, int line)
6494{
6495 static char *ldap_req = "300C020101600702010304008000";
6496
6497 struct tcpcheck_ruleset *rs = NULL;
6498 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6499 struct tcpcheck_rule *chk;
6500 char *errmsg = NULL;
6501 int err_code = 0;
6502
6503 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6504 err_code |= ERR_WARN;
6505
6506 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6507 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006508
6509 curpx->options2 &= ~PR_O2_CHK_ANY;
6510 curpx->options2 |= PR_O2_TCPCHK_CHK;
6511
6512 free_tcpcheck_vars(&rules->preset_vars);
6513 rules->list = NULL;
6514 rules->flags = 0;
6515
Christopher Faulet61cc8522020-04-20 14:54:42 +02006516 rs = find_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006517 if (rs)
6518 goto ruleset_found;
6519
Christopher Faulet61cc8522020-04-20 14:54:42 +02006520 rs = create_tcpcheck_ruleset("*ldap-check");
Christopher Faulet1997eca2020-04-03 23:13:50 +02006521 if (rs == NULL) {
6522 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6523 goto error;
6524 }
6525
6526 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6527 1, curpx, &rs->rules, file, line, &errmsg);
6528 if (!chk) {
6529 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6530 goto error;
6531 }
6532 chk->index = 0;
6533 LIST_ADDQ(&rs->rules, &chk->list);
6534
6535 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6536 "min-recv", "14",
6537 "on-error", "Not LDAPv3 protocol",
6538 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006539 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006540 if (!chk) {
6541 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6542 goto error;
6543 }
6544 chk->index = 1;
6545 LIST_ADDQ(&rs->rules, &chk->list);
6546
6547 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006548 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006549 if (!chk) {
6550 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6551 goto error;
6552 }
6553 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6554 chk->index = 2;
6555 LIST_ADDQ(&rs->rules, &chk->list);
6556
Christopher Fauletd7cee712020-04-21 13:45:00 +02006557 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006558
6559 ruleset_found:
6560 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006561 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006562
6563 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006564 free(errmsg);
6565 return err_code;
6566
6567 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006568 free_tcpcheck_ruleset(rs);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006569 err_code |= ERR_ALERT | ERR_FATAL;
6570 goto out;
6571}
6572
6573int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6574 const char *file, int line)
6575{
6576 struct tcpcheck_ruleset *rs = NULL;
6577 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6578 struct tcpcheck_rule *chk;
6579 char *spop_req = NULL;
6580 char *errmsg = NULL;
6581 int spop_len = 0, err_code = 0;
6582
6583 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6584 err_code |= ERR_WARN;
6585
6586 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6587 goto out;
6588
Christopher Faulet267b01b2020-04-04 10:27:09 +02006589 curpx->options2 &= ~PR_O2_CHK_ANY;
6590 curpx->options2 |= PR_O2_TCPCHK_CHK;
6591
6592 free_tcpcheck_vars(&rules->preset_vars);
6593 rules->list = NULL;
6594 rules->flags = 0;
6595
6596
Christopher Faulet61cc8522020-04-20 14:54:42 +02006597 rs = find_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006598 if (rs)
6599 goto ruleset_found;
6600
Christopher Faulet61cc8522020-04-20 14:54:42 +02006601 rs = create_tcpcheck_ruleset("*spop-check");
Christopher Faulet267b01b2020-04-04 10:27:09 +02006602 if (rs == NULL) {
6603 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6604 goto error;
6605 }
6606
6607 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6608 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6609 goto error;
6610 }
6611 chunk_reset(&trash);
6612 dump_binary(&trash, spop_req, spop_len);
6613 trash.area[trash.data] = '\0';
6614
6615 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6616 1, curpx, &rs->rules, file, line, &errmsg);
6617 if (!chk) {
6618 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6619 goto error;
6620 }
6621 chk->index = 0;
6622 LIST_ADDQ(&rs->rules, &chk->list);
6623
6624 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006625 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006626 if (!chk) {
6627 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6628 goto error;
6629 }
6630 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6631 chk->index = 1;
6632 LIST_ADDQ(&rs->rules, &chk->list);
6633
Christopher Fauletd7cee712020-04-21 13:45:00 +02006634 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006635
6636 ruleset_found:
6637 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006638 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006639
6640 out:
6641 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006642 free(errmsg);
6643 return err_code;
6644
6645 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006646 free_tcpcheck_ruleset(rs);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006647 err_code |= ERR_ALERT | ERR_FATAL;
6648 goto out;
6649}
Christopher Fauletce355072020-04-02 11:44:39 +02006650
Christopher Faulete5870d82020-04-15 11:32:03 +02006651
6652struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6653{
6654 struct tcpcheck_rule *chk = NULL;
6655 struct tcpcheck_http_hdr *hdr = NULL;
6656 char *meth = NULL, *uri = NULL, *vsn = NULL;
6657 char *hdrs, *body;
6658
6659 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6660 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6661 if (hdrs == body)
6662 hdrs = NULL;
6663 if (hdrs) {
6664 *hdrs = '\0';
6665 hdrs +=2;
6666 }
6667 if (body) {
6668 *body = '\0';
6669 body += 4;
6670 }
6671 if (hdrs || body) {
6672 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6673 " Please, consider to use 'http-check send' directive instead.");
6674 }
6675
6676 chk = calloc(1, sizeof(*chk));
6677 if (!chk) {
6678 memprintf(errmsg, "out of memory");
6679 goto error;
6680 }
6681 chk->action = TCPCHK_ACT_SEND;
6682 chk->send.type = TCPCHK_SEND_HTTP;
6683 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6684 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6685 LIST_INIT(&chk->send.http.hdrs);
6686
6687 /* Copy the method, uri and version */
6688 if (*args[cur_arg]) {
6689 if (!*args[cur_arg+1])
6690 uri = args[cur_arg];
6691 else
6692 meth = args[cur_arg];
6693 }
6694 if (*args[cur_arg+1])
6695 uri = args[cur_arg+1];
6696 if (*args[cur_arg+2])
6697 vsn = args[cur_arg+2];
6698
6699 if (meth) {
6700 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6701 chk->send.http.meth.str.area = strdup(meth);
6702 chk->send.http.meth.str.data = strlen(meth);
6703 if (!chk->send.http.meth.str.area) {
6704 memprintf(errmsg, "out of memory");
6705 goto error;
6706 }
6707 }
6708 if (uri) {
6709 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006710 if (!isttest(chk->send.http.uri)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006711 memprintf(errmsg, "out of memory");
6712 goto error;
6713 }
6714 }
6715 if (vsn) {
6716 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006717 if (!isttest(chk->send.http.vsn)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006718 memprintf(errmsg, "out of memory");
6719 goto error;
6720 }
6721 }
6722
6723 /* Copy the header */
6724 if (hdrs) {
6725 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
6726 struct h1m h1m;
6727 int i, ret;
6728
6729 /* Build and parse the request */
6730 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
6731
6732 h1m.flags = H1_MF_HDRS_ONLY;
6733 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
6734 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
6735 &h1m, NULL);
6736 if (ret <= 0) {
6737 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
6738 goto error;
6739 }
6740
Christopher Fauletb61caf42020-04-21 10:57:42 +02006741 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006742 hdr = calloc(1, sizeof(*hdr));
6743 if (!hdr) {
6744 memprintf(errmsg, "out of memory");
6745 goto error;
6746 }
6747 LIST_INIT(&hdr->value);
Christopher Fauletb61caf42020-04-21 10:57:42 +02006748 hdr->name = istdup(tmp_hdrs[i].n);
Christopher Faulete5870d82020-04-15 11:32:03 +02006749 if (!hdr->name.ptr) {
6750 memprintf(errmsg, "out of memory");
6751 goto error;
6752 }
6753
Christopher Fauletb61caf42020-04-21 10:57:42 +02006754 ist0(tmp_hdrs[i].v);
6755 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 +02006756 goto error;
6757 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
6758 }
6759 }
6760
6761 /* Copy the body */
6762 if (body) {
6763 chk->send.http.body = ist2(strdup(body), strlen(body));
Christopher Fauletb61caf42020-04-21 10:57:42 +02006764 if (!isttest(chk->send.http.body)) {
Christopher Faulete5870d82020-04-15 11:32:03 +02006765 memprintf(errmsg, "out of memory");
6766 goto error;
6767 }
6768 }
6769
6770 return chk;
6771
6772 error:
6773 free_tcpcheck_http_hdr(hdr);
6774 free_tcpcheck(chk, 0);
6775 return NULL;
6776}
6777
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006778int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6779 const char *file, int line)
6780{
Christopher Faulete5870d82020-04-15 11:32:03 +02006781 struct tcpcheck_ruleset *rs = NULL;
6782 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6783 struct tcpcheck_rule *chk;
6784 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006785 int err_code = 0;
6786
6787 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6788 err_code |= ERR_WARN;
6789
6790 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6791 goto out;
6792
Christopher Faulete5870d82020-04-15 11:32:03 +02006793 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
6794 if (!chk) {
6795 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6796 goto error;
6797 }
6798 if (errmsg) {
6799 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
6800 err_code |= ERR_WARN;
6801 free(errmsg);
6802 errmsg = NULL;
6803 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006804
Christopher Faulete5870d82020-04-15 11:32:03 +02006805 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006806 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02006807 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006808
Christopher Faulete5870d82020-04-15 11:32:03 +02006809 free_tcpcheck_vars(&rules->preset_vars);
6810 rules->list = NULL;
6811 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006812
Christopher Faulete5870d82020-04-15 11:32:03 +02006813 /* Deduce the ruleset name from the proxy info */
6814 chunk_printf(&trash, "*http-check-%s_%s-%d",
6815 ((curpx == defpx) ? "defaults" : curpx->id),
6816 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006817
Christopher Faulet61cc8522020-04-20 14:54:42 +02006818 rs = find_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006819 if (rs == NULL) {
Christopher Faulet61cc8522020-04-20 14:54:42 +02006820 rs = create_tcpcheck_ruleset(b_orig(&trash));
Christopher Faulete5870d82020-04-15 11:32:03 +02006821 if (rs == NULL) {
6822 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6823 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006824 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006825 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006826
Christopher Faulete5870d82020-04-15 11:32:03 +02006827 rules->list = &rs->rules;
6828 rules->flags |= TCPCHK_RULES_HTTP_CHK;
6829 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
6830 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6831 rules->list = NULL;
6832 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006833 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006834
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006835 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02006836 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006837 return err_code;
6838
6839 error:
Christopher Faulet61cc8522020-04-20 14:54:42 +02006840 free_tcpcheck_ruleset(rs);
Christopher Faulete5870d82020-04-15 11:32:03 +02006841 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006842 err_code |= ERR_ALERT | ERR_FATAL;
6843 goto out;
6844}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006845
Christopher Faulet6f557912020-04-09 15:58:50 +02006846int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6847 const char *file, int line)
6848{
6849 int err_code = 0;
6850
Christopher Faulet6f557912020-04-09 15:58:50 +02006851 curpx->options2 &= ~PR_O2_CHK_ANY;
6852 curpx->options2 |= PR_O2_EXT_CHK;
6853 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6854 goto out;
6855
6856 out:
6857 return err_code;
6858}
6859
Christopher Fauletce8111e2020-04-06 15:04:11 +02006860/* Parse the "addr" server keyword */
6861static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6862 char **errmsg)
6863{
6864 struct sockaddr_storage *sk;
6865 struct protocol *proto;
6866 int port1, port2, err_code = 0;
6867
6868
6869 if (!*args[*cur_arg+1]) {
6870 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6871 goto error;
6872 }
6873
6874 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6875 if (!sk) {
6876 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6877 goto error;
6878 }
6879
6880 proto = protocol_by_family(sk->ss_family);
6881 if (!proto || !proto->connect) {
6882 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6883 args[*cur_arg], args[*cur_arg+1]);
6884 goto error;
6885 }
6886
6887 if (port1 != port2) {
6888 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6889 args[*cur_arg], args[*cur_arg+1]);
6890 goto error;
6891 }
6892
6893 srv->check.addr = srv->agent.addr = *sk;
6894 srv->flags |= SRV_F_CHECKADDR;
6895 srv->flags |= SRV_F_AGENTADDR;
6896
6897 out:
6898 return err_code;
6899
6900 error:
6901 err_code |= ERR_ALERT | ERR_FATAL;
6902 goto out;
6903}
6904
6905
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006906/* Parse the "agent-addr" server keyword */
6907static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6908 char **errmsg)
6909{
6910 int err_code = 0;
6911
6912 if (!*(args[*cur_arg+1])) {
6913 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6914 goto error;
6915 }
6916 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6917 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6918 goto error;
6919 }
6920
6921 out:
6922 return err_code;
6923
6924 error:
6925 err_code |= ERR_ALERT | ERR_FATAL;
6926 goto out;
6927}
6928
6929/* Parse the "agent-check" server keyword */
6930static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6931 char **errmsg)
6932{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006933 struct tcpcheck_ruleset *rs = NULL;
6934 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6935 struct tcpcheck_rule *chk;
6936 int err_code = 0;
6937
6938 if (srv->do_agent)
6939 goto out;
6940
6941 if (!rules) {
6942 rules = calloc(1, sizeof(*rules));
6943 if (!rules) {
6944 memprintf(errmsg, "out of memory.");
6945 goto error;
6946 }
6947 LIST_INIT(&rules->preset_vars);
6948 srv->agent.tcpcheck_rules = rules;
6949 }
6950 rules->list = NULL;
6951 rules->flags = 0;
6952
Christopher Faulet61cc8522020-04-20 14:54:42 +02006953 rs = find_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006954 if (rs)
6955 goto ruleset_found;
6956
Christopher Faulet61cc8522020-04-20 14:54:42 +02006957 rs = create_tcpcheck_ruleset("*agent-check");
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006958 if (rs == NULL) {
6959 memprintf(errmsg, "out of memory.");
6960 goto error;
6961 }
6962
6963 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6964 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6965 if (!chk) {
6966 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6967 goto error;
6968 }
6969 chk->index = 0;
6970 LIST_ADDQ(&rs->rules, &chk->list);
6971
6972 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006973 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
6974 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006975 if (!chk) {
6976 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6977 goto error;
6978 }
6979 chk->expect.custom = tcpcheck_agent_expect_reply;
6980 chk->index = 1;
6981 LIST_ADDQ(&rs->rules, &chk->list);
6982
Christopher Fauletd7cee712020-04-21 13:45:00 +02006983 ebis_insert(&shared_tcpchecks, &rs->node);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006984
6985 ruleset_found:
6986 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006987 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006988 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006989
6990 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006991 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006992
6993 error:
6994 deinit_srv_agent_check(srv);
Christopher Faulet61cc8522020-04-20 14:54:42 +02006995 free_tcpcheck_ruleset(rs);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006996 err_code |= ERR_ALERT | ERR_FATAL;
6997 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006998}
6999
7000/* Parse the "agent-inter" server keyword */
7001static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7002 char **errmsg)
7003{
7004 const char *err = NULL;
7005 unsigned int delay;
7006 int err_code = 0;
7007
7008 if (!*(args[*cur_arg+1])) {
7009 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7010 goto error;
7011 }
7012
7013 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7014 if (err == PARSE_TIME_OVER) {
7015 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7016 args[*cur_arg+1], args[*cur_arg], srv->id);
7017 goto error;
7018 }
7019 else if (err == PARSE_TIME_UNDER) {
7020 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7021 args[*cur_arg+1], args[*cur_arg], srv->id);
7022 goto error;
7023 }
7024 else if (err) {
7025 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7026 *err, srv->id);
7027 goto error;
7028 }
7029 if (delay <= 0) {
7030 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7031 delay, args[*cur_arg], srv->id);
7032 goto error;
7033 }
7034 srv->agent.inter = delay;
7035
7036 out:
7037 return err_code;
7038
7039 error:
7040 err_code |= ERR_ALERT | ERR_FATAL;
7041 goto out;
7042}
7043
7044/* Parse the "agent-port" server keyword */
7045static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7046 char **errmsg)
7047{
7048 int err_code = 0;
7049
7050 if (!*(args[*cur_arg+1])) {
7051 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7052 goto error;
7053 }
7054
7055 global.maxsock++;
7056 srv->agent.port = atol(args[*cur_arg+1]);
7057
7058 out:
7059 return err_code;
7060
7061 error:
7062 err_code |= ERR_ALERT | ERR_FATAL;
7063 goto out;
7064}
7065
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007066int set_srv_agent_send(struct server *srv, const char *send)
7067{
7068 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7069 struct tcpcheck_var *var = NULL;
7070 char *str;
7071
7072 str = strdup(send);
Christopher Fauletb61caf42020-04-21 10:57:42 +02007073 var = create_tcpcheck_var(ist("check.agent_string"));
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007074 if (str == NULL || var == NULL)
7075 goto error;
7076
7077 free_tcpcheck_vars(&rules->preset_vars);
7078
7079 var->data.type = SMP_T_STR;
7080 var->data.u.str.area = str;
7081 var->data.u.str.data = strlen(str);
7082 LIST_INIT(&var->list);
7083 LIST_ADDQ(&rules->preset_vars, &var->list);
7084
7085 return 1;
7086
7087 error:
7088 free(str);
7089 free(var);
7090 return 0;
7091}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007092
7093/* Parse the "agent-send" server keyword */
7094static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7095 char **errmsg)
7096{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007097 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007098 int err_code = 0;
7099
7100 if (!*(args[*cur_arg+1])) {
7101 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7102 goto error;
7103 }
7104
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007105 if (!rules) {
7106 rules = calloc(1, sizeof(*rules));
7107 if (!rules) {
7108 memprintf(errmsg, "out of memory.");
7109 goto error;
7110 }
7111 LIST_INIT(&rules->preset_vars);
7112 srv->agent.tcpcheck_rules = rules;
7113 }
7114
7115 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007116 memprintf(errmsg, "out of memory.");
7117 goto error;
7118 }
7119
7120 out:
7121 return err_code;
7122
7123 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007124 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007125 err_code |= ERR_ALERT | ERR_FATAL;
7126 goto out;
7127}
7128
7129/* Parse the "no-agent-send" server keyword */
7130static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7131 char **errmsg)
7132{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007133 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007134 return 0;
7135}
7136
Christopher Fauletce8111e2020-04-06 15:04:11 +02007137/* Parse the "check" server keyword */
7138static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7139 char **errmsg)
7140{
7141 srv->do_check = 1;
7142 return 0;
7143}
7144
7145/* Parse the "check-send-proxy" server keyword */
7146static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7147 char **errmsg)
7148{
7149 srv->check.send_proxy = 1;
7150 return 0;
7151}
7152
7153/* Parse the "check-via-socks4" server keyword */
7154static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7155 char **errmsg)
7156{
7157 srv->check.via_socks4 = 1;
7158 return 0;
7159}
7160
7161/* Parse the "no-check" server keyword */
7162static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7163 char **errmsg)
7164{
7165 deinit_srv_check(srv);
7166 return 0;
7167}
7168
7169/* Parse the "no-check-send-proxy" server keyword */
7170static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7171 char **errmsg)
7172{
7173 srv->check.send_proxy = 0;
7174 return 0;
7175}
7176
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007177/* parse the "check-proto" server keyword */
7178static int srv_parse_check_proto(char **args, int *cur_arg,
7179 struct proxy *px, struct server *newsrv, char **err)
7180{
7181 int err_code = 0;
7182
7183 if (!*args[*cur_arg + 1]) {
7184 memprintf(err, "'%s' : missing value", args[*cur_arg]);
7185 goto error;
7186 }
7187 newsrv->check.mux_proto = get_mux_proto(ist2(args[*cur_arg + 1], strlen(args[*cur_arg + 1])));
7188 if (!newsrv->check.mux_proto) {
7189 memprintf(err, "'%s' : unknown MUX protocol '%s'", args[*cur_arg], args[*cur_arg+1]);
7190 goto error;
7191 }
7192
7193 out:
7194 return err_code;
7195
7196 error:
7197 err_code |= ERR_ALERT | ERR_FATAL;
7198 goto out;
7199}
7200
7201
Christopher Fauletce8111e2020-04-06 15:04:11 +02007202/* Parse the "rise" server keyword */
7203static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7204 char **errmsg)
7205{
7206 int err_code = 0;
7207
7208 if (!*args[*cur_arg + 1]) {
7209 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7210 goto error;
7211 }
7212
7213 srv->check.rise = atol(args[*cur_arg+1]);
7214 if (srv->check.rise <= 0) {
7215 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7216 goto error;
7217 }
7218
7219 if (srv->check.health)
7220 srv->check.health = srv->check.rise;
7221
7222 out:
7223 return err_code;
7224
7225 error:
7226 deinit_srv_agent_check(srv);
7227 err_code |= ERR_ALERT | ERR_FATAL;
7228 goto out;
7229 return 0;
7230}
7231
7232/* Parse the "fall" server keyword */
7233static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7234 char **errmsg)
7235{
7236 int err_code = 0;
7237
7238 if (!*args[*cur_arg + 1]) {
7239 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7240 goto error;
7241 }
7242
7243 srv->check.fall = atol(args[*cur_arg+1]);
7244 if (srv->check.fall <= 0) {
7245 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7246 goto error;
7247 }
7248
7249 out:
7250 return err_code;
7251
7252 error:
7253 deinit_srv_agent_check(srv);
7254 err_code |= ERR_ALERT | ERR_FATAL;
7255 goto out;
7256 return 0;
7257}
7258
7259/* Parse the "inter" server keyword */
7260static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7261 char **errmsg)
7262{
7263 const char *err = NULL;
7264 unsigned int delay;
7265 int err_code = 0;
7266
7267 if (!*(args[*cur_arg+1])) {
7268 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7269 goto error;
7270 }
7271
7272 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7273 if (err == PARSE_TIME_OVER) {
7274 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7275 args[*cur_arg+1], args[*cur_arg], srv->id);
7276 goto error;
7277 }
7278 else if (err == PARSE_TIME_UNDER) {
7279 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7280 args[*cur_arg+1], args[*cur_arg], srv->id);
7281 goto error;
7282 }
7283 else if (err) {
7284 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7285 *err, srv->id);
7286 goto error;
7287 }
7288 if (delay <= 0) {
7289 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7290 delay, args[*cur_arg], srv->id);
7291 goto error;
7292 }
7293 srv->check.inter = delay;
7294
7295 out:
7296 return err_code;
7297
7298 error:
7299 err_code |= ERR_ALERT | ERR_FATAL;
7300 goto out;
7301}
7302
7303
7304/* Parse the "fastinter" server keyword */
7305static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7306 char **errmsg)
7307{
7308 const char *err = NULL;
7309 unsigned int delay;
7310 int err_code = 0;
7311
7312 if (!*(args[*cur_arg+1])) {
7313 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7314 goto error;
7315 }
7316
7317 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7318 if (err == PARSE_TIME_OVER) {
7319 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7320 args[*cur_arg+1], args[*cur_arg], srv->id);
7321 goto error;
7322 }
7323 else if (err == PARSE_TIME_UNDER) {
7324 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7325 args[*cur_arg+1], args[*cur_arg], srv->id);
7326 goto error;
7327 }
7328 else if (err) {
7329 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7330 *err, srv->id);
7331 goto error;
7332 }
7333 if (delay <= 0) {
7334 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7335 delay, args[*cur_arg], srv->id);
7336 goto error;
7337 }
7338 srv->check.fastinter = delay;
7339
7340 out:
7341 return err_code;
7342
7343 error:
7344 err_code |= ERR_ALERT | ERR_FATAL;
7345 goto out;
7346}
7347
7348
7349/* Parse the "downinter" server keyword */
7350static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7351 char **errmsg)
7352{
7353 const char *err = NULL;
7354 unsigned int delay;
7355 int err_code = 0;
7356
7357 if (!*(args[*cur_arg+1])) {
7358 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7359 goto error;
7360 }
7361
7362 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7363 if (err == PARSE_TIME_OVER) {
7364 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7365 args[*cur_arg+1], args[*cur_arg], srv->id);
7366 goto error;
7367 }
7368 else if (err == PARSE_TIME_UNDER) {
7369 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7370 args[*cur_arg+1], args[*cur_arg], srv->id);
7371 goto error;
7372 }
7373 else if (err) {
7374 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7375 *err, srv->id);
7376 goto error;
7377 }
7378 if (delay <= 0) {
7379 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7380 delay, args[*cur_arg], srv->id);
7381 goto error;
7382 }
7383 srv->check.downinter = delay;
7384
7385 out:
7386 return err_code;
7387
7388 error:
7389 err_code |= ERR_ALERT | ERR_FATAL;
7390 goto out;
7391}
7392
7393/* Parse the "port" server keyword */
7394static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7395 char **errmsg)
7396{
7397 int err_code = 0;
7398
7399 if (!*(args[*cur_arg+1])) {
7400 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7401 goto error;
7402 }
7403
7404 global.maxsock++;
7405 srv->check.port = atol(args[*cur_arg+1]);
7406 srv->flags |= SRV_F_CHECKPORT;
7407
7408 out:
7409 return err_code;
7410
7411 error:
7412 err_code |= ERR_ALERT | ERR_FATAL;
7413 goto out;
7414}
7415
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007416static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007417 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7418 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7419 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007420 { 0, NULL, NULL },
7421}};
7422
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007423static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007424 { "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 +02007425 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7426 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7427 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7428 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7429 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007430 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
Christopher Fauletedc6ed92020-04-23 16:27:59 +02007431 { "check-proto", srv_parse_check_proto, 1, 1 }, /* Set the mux protocol for health checks */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007432 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7433 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007434 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007435 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7436 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7437 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7438 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7439 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7440 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7441 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7442 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007443 { NULL, NULL, 0 },
7444}};
7445
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007446INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007447INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007448
Willy Tarreaubd741542010-03-16 18:46:54 +01007449/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007450 * Local variables:
7451 * c-indent-level: 8
7452 * c-basic-offset: 8
7453 * End:
7454 */