blob: a94a29cd2444e38fe1688269875c57e2c3a7b51a [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>
Willy Tarreaubaaee002006-06-26 02:48:02 +020041
42#include <types/global.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020043#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010044#include <types/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020045
Gaetan Rivet707b52f2020-02-21 18:14:59 +010046#include <proto/action.h>
Christopher Fauletba3c68f2020-04-01 16:27:05 +020047#include <proto/arg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020048#include <proto/backend.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020049#include <proto/checks.h>
William Lallemand9ed62032016-11-21 17:49:11 +010050#include <proto/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020051#include <proto/fd.h>
52#include <proto/log.h>
Willy Tarreau53a47662017-08-28 10:53:00 +020053#include <proto/mux_pt.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020054#include <proto/queue.h>
Willy Tarreauc6f4ce82009-06-10 11:09:37 +020055#include <proto/port_range.h>
Willy Tarreaue8c66af2008-01-13 18:40:14 +010056#include <proto/proto_tcp.h>
Baptiste Assmann69e273f2013-12-11 00:52:19 +010057#include <proto/protocol.h>
Willy Tarreau2b5652f2006-12-31 17:46:05 +010058#include <proto/proxy.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020059#include <proto/server.h>
Willy Tarreau48d6bf22016-06-21 16:27:34 +020060#include <proto/signal.h>
Willy Tarreau9e000c62011-03-10 14:03:36 +010061#include <proto/stream_interface.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020062#include <proto/task.h>
Gaetan Rivet13a50432020-02-21 18:13:44 +010063#include <proto/vars.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020064#include <proto/log.h>
65#include <proto/dns.h>
66#include <proto/proto_udp.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020067#include <proto/ssl_sock.h>
Christopher Fauletb7d30092020-03-30 15:19:03 +020068#include <proto/sample.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020069
Willy Tarreaubd741542010-03-16 18:46:54 +010070static int httpchk_expect(struct server *s, int done);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +020071static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *);
Willy Tarreau6bdcab02017-10-04 18:41:00 +020072static int tcpcheck_main(struct check *);
Olivier Houchard910b2bc2018-07-17 18:49:38 +020073static void __event_srv_chk_w(struct conn_stream *cs);
Olivier Houchard4501c3e2018-08-28 19:36:18 +020074static int wake_srv_chk(struct conn_stream *cs);
Olivier Houchardaf4021e2018-08-09 13:06:55 +020075static void __event_srv_chk_r(struct conn_stream *cs);
Willy Tarreaubd741542010-03-16 18:46:54 +010076
Christopher Faulet31c30fd2020-03-26 21:10:03 +010077static int srv_check_healthcheck_port(struct check *chk);
78
Christopher Faulet5d503fc2020-03-30 20:34:34 +020079/* Global list to share all tcp-checks */
80struct list tcpchecks_list = LIST_HEAD_INIT(tcpchecks_list);
81
82
Willy Tarreau8ceae722018-11-26 11:58:30 +010083DECLARE_STATIC_POOL(pool_head_email_alert, "email_alert", sizeof(struct email_alert));
84DECLARE_STATIC_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
Christopher Faulet31dff9b2017-10-23 15:45:20 +020085
Gaetan Rivet05d692d2020-02-14 17:42:54 +010086/* Dummy frontend used to create all checks sessions. */
87static struct proxy checks_fe;
Christopher Faulet31dff9b2017-10-23 15:45:20 +020088
Simon Horman63a4a822012-03-19 07:24:41 +090089static const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +010090 [HCHK_STATUS_UNKNOWN] = { CHK_RES_UNKNOWN, "UNK", "Unknown" },
91 [HCHK_STATUS_INI] = { CHK_RES_UNKNOWN, "INI", "Initializing" },
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +020092 [HCHK_STATUS_START] = { /* SPECIAL STATUS*/ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020093
Willy Tarreau23964182014-05-20 20:56:30 +020094 /* Below we have finished checks */
95 [HCHK_STATUS_CHECKED] = { CHK_RES_NEUTRAL, "CHECKED", "No status change" },
Willy Tarreau6aaa1b82013-12-11 17:09:34 +010096 [HCHK_STATUS_HANA] = { CHK_RES_FAILED, "HANA", "Health analyze" },
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +010097
Willy Tarreau6aaa1b82013-12-11 17:09:34 +010098 [HCHK_STATUS_SOCKERR] = { CHK_RES_FAILED, "SOCKERR", "Socket error" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020099
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100100 [HCHK_STATUS_L4OK] = { CHK_RES_PASSED, "L4OK", "Layer4 check passed" },
101 [HCHK_STATUS_L4TOUT] = { CHK_RES_FAILED, "L4TOUT", "Layer4 timeout" },
102 [HCHK_STATUS_L4CON] = { CHK_RES_FAILED, "L4CON", "Layer4 connection problem" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200103
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100104 [HCHK_STATUS_L6OK] = { CHK_RES_PASSED, "L6OK", "Layer6 check passed" },
105 [HCHK_STATUS_L6TOUT] = { CHK_RES_FAILED, "L6TOUT", "Layer6 timeout" },
106 [HCHK_STATUS_L6RSP] = { CHK_RES_FAILED, "L6RSP", "Layer6 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200107
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100108 [HCHK_STATUS_L7TOUT] = { CHK_RES_FAILED, "L7TOUT", "Layer7 timeout" },
109 [HCHK_STATUS_L7RSP] = { CHK_RES_FAILED, "L7RSP", "Layer7 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200110
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200111 [HCHK_STATUS_L57DATA] = { /* DUMMY STATUS */ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200112
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100113 [HCHK_STATUS_L7OKD] = { CHK_RES_PASSED, "L7OK", "Layer7 check passed" },
114 [HCHK_STATUS_L7OKCD] = { CHK_RES_CONDPASS, "L7OKC", "Layer7 check conditionally passed" },
115 [HCHK_STATUS_L7STS] = { CHK_RES_FAILED, "L7STS", "Layer7 wrong status" },
Simon Horman98637e52014-06-20 12:30:16 +0900116
117 [HCHK_STATUS_PROCERR] = { CHK_RES_FAILED, "PROCERR", "External check error" },
118 [HCHK_STATUS_PROCTOUT] = { CHK_RES_FAILED, "PROCTOUT", "External check timeout" },
Cyril Bonté77010d82014-08-07 01:55:37 +0200119 [HCHK_STATUS_PROCOK] = { CHK_RES_PASSED, "PROCOK", "External check passed" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200120};
121
Cyril Bontéac92a062014-12-27 22:28:38 +0100122const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
123 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
124 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
125 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
126 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
127 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
128 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
129 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
Christopher Fauletaaae9a02020-04-26 09:50:31 +0200130 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
131 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
Cyril Bontéac92a062014-12-27 22:28:38 +0100132 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
133 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
134};
135
Simon Horman63a4a822012-03-19 07:24:41 +0900136static const struct analyze_status analyze_statuses[HANA_STATUS_SIZE] = { /* 0: ignore, 1: error, 2: OK */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100137 [HANA_STATUS_UNKNOWN] = { "Unknown", { 0, 0 }},
138
139 [HANA_STATUS_L4_OK] = { "L4 successful connection", { 2, 0 }},
140 [HANA_STATUS_L4_ERR] = { "L4 unsuccessful connection", { 1, 1 }},
141
142 [HANA_STATUS_HTTP_OK] = { "Correct http response", { 0, 2 }},
143 [HANA_STATUS_HTTP_STS] = { "Wrong http response", { 0, 1 }},
144 [HANA_STATUS_HTTP_HDRRSP] = { "Invalid http response (headers)", { 0, 1 }},
145 [HANA_STATUS_HTTP_RSP] = { "Invalid http response", { 0, 1 }},
146
147 [HANA_STATUS_HTTP_READ_ERROR] = { "Read error (http)", { 0, 1 }},
148 [HANA_STATUS_HTTP_READ_TIMEOUT] = { "Read timeout (http)", { 0, 1 }},
149 [HANA_STATUS_HTTP_BROKEN_PIPE] = { "Close from server (http)", { 0, 1 }},
150};
151
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100152/* checks if <err> is a real error for errno or one that can be ignored, and
153 * return 0 for these ones or <err> for real ones.
154 */
155static inline int unclean_errno(int err)
156{
157 if (err == EAGAIN || err == EINPROGRESS ||
158 err == EISCONN || err == EALREADY)
159 return 0;
160 return err;
161}
162
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200163/*
164 * Convert check_status code to description
165 */
166const char *get_check_status_description(short check_status) {
167
168 const char *desc;
169
170 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200171 desc = check_statuses[check_status].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200172 else
173 desc = NULL;
174
175 if (desc && *desc)
176 return desc;
177 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200178 return check_statuses[HCHK_STATUS_UNKNOWN].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200179}
180
181/*
182 * Convert check_status code to short info
183 */
184const char *get_check_status_info(short check_status) {
185
186 const char *info;
187
188 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200189 info = check_statuses[check_status].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200190 else
191 info = NULL;
192
193 if (info && *info)
194 return info;
195 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200196 return check_statuses[HCHK_STATUS_UNKNOWN].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200197}
198
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
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200214/*
Simon Horman4a741432013-02-23 15:35:38 +0900215 * Set check->status, update check->duration and fill check->result with
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200216 * an adequate CHK_RES_* value. The new check->health is computed based
217 * on the result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200218 *
219 * Show information in logs about failed health check if server is UP
220 * or succeeded health checks if server is DOWN.
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200221 */
Simon Horman4a741432013-02-23 15:35:38 +0900222static void set_server_check_status(struct check *check, short status, const char *desc)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100223{
Simon Horman4a741432013-02-23 15:35:38 +0900224 struct server *s = check->server;
Willy Tarreaubef1b322014-05-13 21:01:39 +0200225 short prev_status = check->status;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200226 int report = 0;
Simon Horman4a741432013-02-23 15:35:38 +0900227
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200228 if (status == HCHK_STATUS_START) {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100229 check->result = CHK_RES_UNKNOWN; /* no result yet */
Simon Horman4a741432013-02-23 15:35:38 +0900230 check->desc[0] = '\0';
231 check->start = now;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200232 return;
233 }
234
Simon Horman4a741432013-02-23 15:35:38 +0900235 if (!check->status)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200236 return;
237
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200238 if (desc && *desc) {
Simon Horman4a741432013-02-23 15:35:38 +0900239 strncpy(check->desc, desc, HCHK_DESC_LEN-1);
240 check->desc[HCHK_DESC_LEN-1] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200241 } else
Simon Horman4a741432013-02-23 15:35:38 +0900242 check->desc[0] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200243
Simon Horman4a741432013-02-23 15:35:38 +0900244 check->status = status;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200245 if (check_statuses[status].result)
Simon Horman4a741432013-02-23 15:35:38 +0900246 check->result = check_statuses[status].result;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200247
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100248 if (status == HCHK_STATUS_HANA)
Simon Horman4a741432013-02-23 15:35:38 +0900249 check->duration = -1;
250 else if (!tv_iszero(&check->start)) {
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200251 /* set_server_check_status() may be called more than once */
Simon Horman4a741432013-02-23 15:35:38 +0900252 check->duration = tv_ms_elapsed(&check->start, &now);
253 tv_zero(&check->start);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200254 }
255
Willy Tarreau23964182014-05-20 20:56:30 +0200256 /* no change is expected if no state change occurred */
257 if (check->result == CHK_RES_NEUTRAL)
258 return;
259
Olivier Houchard0923fa42019-01-11 18:43:04 +0100260 /* If the check was really just sending a mail, it won't have an
261 * associated server, so we're done now.
262 */
263 if (!s)
264 return;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200265 report = 0;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200266
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200267 switch (check->result) {
268 case CHK_RES_FAILED:
Willy Tarreau12634e12014-05-23 11:32:36 +0200269 /* Failure to connect to the agent as a secondary check should not
270 * cause the server to be marked down.
271 */
272 if ((!(check->state & CHK_ST_AGENT) ||
Simon Hormaneaabd522015-02-26 11:26:17 +0900273 (check->status >= HCHK_STATUS_L57DATA)) &&
Christopher Fauletb119a792018-05-02 12:12:45 +0200274 (check->health > 0)) {
Olivier Houchard7059c552019-03-08 18:49:32 +0100275 _HA_ATOMIC_ADD(&s->counters.failed_checks, 1);
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200276 report = 1;
277 check->health--;
278 if (check->health < check->rise)
279 check->health = 0;
280 }
281 break;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200282
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200283 case CHK_RES_PASSED:
284 case CHK_RES_CONDPASS: /* "condpass" cannot make the first step but it OK after a "passed" */
285 if ((check->health < check->rise + check->fall - 1) &&
286 (check->result == CHK_RES_PASSED || check->health > 0)) {
287 report = 1;
288 check->health++;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200289
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200290 if (check->health >= check->rise)
291 check->health = check->rise + check->fall - 1; /* OK now */
292 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200293
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200294 /* clear consecutive_errors if observing is enabled */
295 if (s->onerror)
296 s->consecutive_errors = 0;
297 break;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100298
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200299 default:
300 break;
301 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200302
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200303 if (s->proxy->options2 & PR_O2_LOGHCHKS &&
304 (status != prev_status || report)) {
305 chunk_printf(&trash,
Willy Tarreau12634e12014-05-23 11:32:36 +0200306 "%s check for %sserver %s/%s %s%s",
307 (check->state & CHK_ST_AGENT) ? "Agent" : "Health",
Willy Tarreauc93cd162014-05-13 15:54:22 +0200308 s->flags & SRV_F_BACKUP ? "backup " : "",
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100309 s->proxy->id, s->id,
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100310 (check->result == CHK_RES_CONDPASS) ? "conditionally ":"",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200311 (check->result >= CHK_RES_PASSED) ? "succeeded" : "failed");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200312
Emeric Brun5a133512017-10-19 14:42:30 +0200313 srv_append_status(&trash, s, check, -1, 0);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200314
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100315 chunk_appendf(&trash, ", status: %d/%d %s",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200316 (check->health >= check->rise) ? check->health - check->rise + 1 : check->health,
317 (check->health >= check->rise) ? check->fall : check->rise,
318 (check->health >= check->rise) ? (s->uweight ? "UP" : "DRAIN") : "DOWN");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200319
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200320 ha_warning("%s.\n", trash.area);
321 send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area);
322 send_email_alert(s, LOG_INFO, "%s", trash.area);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200323 }
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200324}
325
Willy Tarreau4eec5472014-05-20 22:32:27 +0200326/* Marks the check <check>'s server down if the current check is already failed
327 * and the server is not down yet nor in maintenance.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200328 */
Willy Tarreau4eec5472014-05-20 22:32:27 +0200329static void check_notify_failure(struct check *check)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200330{
Simon Horman4a741432013-02-23 15:35:38 +0900331 struct server *s = check->server;
Simon Hormane0d1bfb2011-06-21 14:34:58 +0900332
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200333 /* The agent secondary check should only cause a server to be marked
334 * as down if check->status is HCHK_STATUS_L7STS, which indicates
335 * that the agent returned "fail", "stopped" or "down".
336 * The implication here is that failure to connect to the agent
337 * as a secondary check should not cause the server to be marked
338 * down. */
339 if ((check->state & CHK_ST_AGENT) && check->status != HCHK_STATUS_L7STS)
340 return;
341
Willy Tarreau4eec5472014-05-20 22:32:27 +0200342 if (check->health > 0)
343 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100344
Willy Tarreau4eec5472014-05-20 22:32:27 +0200345 /* We only report a reason for the check if we did not do so previously */
Emeric Brun5a133512017-10-19 14:42:30 +0200346 srv_set_stopped(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200347}
348
Willy Tarreauaf549582014-05-16 17:37:50 +0200349/* Marks the check <check> as valid and tries to set its server up, provided
Willy Tarreau3e048382014-05-21 10:30:54 +0200350 * it isn't in maintenance, it is not tracking a down server and other checks
351 * comply. The rule is simple : by default, a server is up, unless any of the
352 * following conditions is true :
353 * - health check failed (check->health < rise)
354 * - agent check failed (agent->health < rise)
355 * - the server tracks a down server (track && track->state == STOPPED)
356 * Note that if the server has a slowstart, it will switch to STARTING instead
357 * of RUNNING. Also, only the health checks support the nolb mode, so the
358 * agent's success may not take the server out of this mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200359 */
Willy Tarreau3e048382014-05-21 10:30:54 +0200360static void check_notify_success(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200361{
Simon Horman4a741432013-02-23 15:35:38 +0900362 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100363
Emeric Brun52a91d32017-08-31 14:41:55 +0200364 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200365 return;
Cyril Bontécd19e512010-01-31 22:34:03 +0100366
Emeric Brun52a91d32017-08-31 14:41:55 +0200367 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreauaf549582014-05-16 17:37:50 +0200368 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100369
Willy Tarreau3e048382014-05-21 10:30:54 +0200370 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
371 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100372
Willy Tarreau3e048382014-05-21 10:30:54 +0200373 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
374 return;
Willy Tarreauaf549582014-05-16 17:37:50 +0200375
Emeric Brun52a91d32017-08-31 14:41:55 +0200376 if ((check->state & CHK_ST_AGENT) && s->next_state == SRV_ST_STOPPING)
Willy Tarreau3e048382014-05-21 10:30:54 +0200377 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100378
Emeric Brun5a133512017-10-19 14:42:30 +0200379 srv_set_running(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100380}
381
Willy Tarreaudb58b792014-05-21 13:57:23 +0200382/* Marks the check <check> as valid and tries to set its server into stopping mode
383 * if it was running or starting, and provided it isn't in maintenance and other
384 * checks comply. The conditions for the server to be marked in stopping mode are
385 * the same as for it to be turned up. Also, only the health checks support the
386 * nolb mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200387 */
Willy Tarreaudb58b792014-05-21 13:57:23 +0200388static void check_notify_stopping(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200389{
Simon Horman4a741432013-02-23 15:35:38 +0900390 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100391
Emeric Brun52a91d32017-08-31 14:41:55 +0200392 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200393 return;
394
Willy Tarreaudb58b792014-05-21 13:57:23 +0200395 if (check->state & CHK_ST_AGENT)
396 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100397
Emeric Brun52a91d32017-08-31 14:41:55 +0200398 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreaudb58b792014-05-21 13:57:23 +0200399 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100400
Willy Tarreaudb58b792014-05-21 13:57:23 +0200401 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
402 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100403
Willy Tarreaudb58b792014-05-21 13:57:23 +0200404 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
405 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100406
Willy Tarreaub26881a2017-12-23 11:16:49 +0100407 srv_set_stopping(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100408}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200409
Willy Tarreau9fe7aae2013-12-31 23:47:37 +0100410/* note: use health_adjust() only, which first checks that the observe mode is
411 * enabled.
412 */
413void __health_adjust(struct server *s, short status)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100414{
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100415 int failed;
416 int expire;
417
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100418 if (s->observe >= HANA_OBS_SIZE)
419 return;
420
Willy Tarreaubb956662013-01-24 00:37:39 +0100421 if (status >= HANA_STATUS_SIZE || !analyze_statuses[status].desc)
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100422 return;
423
424 switch (analyze_statuses[status].lr[s->observe - 1]) {
425 case 1:
426 failed = 1;
427 break;
428
429 case 2:
430 failed = 0;
431 break;
432
433 default:
434 return;
435 }
436
437 if (!failed) {
438 /* good: clear consecutive_errors */
439 s->consecutive_errors = 0;
440 return;
441 }
442
Olivier Houchard7059c552019-03-08 18:49:32 +0100443 _HA_ATOMIC_ADD(&s->consecutive_errors, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100444
445 if (s->consecutive_errors < s->consecutive_errors_limit)
446 return;
447
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100448 chunk_printf(&trash, "Detected %d consecutive errors, last one was: %s",
449 s->consecutive_errors, get_analyze_status(status));
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100450
451 switch (s->onerror) {
452 case HANA_ONERR_FASTINTER:
453 /* force fastinter - nothing to do here as all modes force it */
454 break;
455
456 case HANA_ONERR_SUDDTH:
457 /* simulate a pre-fatal failed health check */
Simon Horman58c32972013-11-25 10:46:38 +0900458 if (s->check.health > s->check.rise)
459 s->check.health = s->check.rise + 1;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100460
461 /* no break - fall through */
462
463 case HANA_ONERR_FAILCHK:
464 /* simulate a failed health check */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200465 set_server_check_status(&s->check, HCHK_STATUS_HANA,
466 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200467 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100468 break;
469
470 case HANA_ONERR_MARKDWN:
471 /* mark server down */
Simon Horman58c32972013-11-25 10:46:38 +0900472 s->check.health = s->check.rise;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200473 set_server_check_status(&s->check, HCHK_STATUS_HANA,
474 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200475 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100476 break;
477
478 default:
479 /* write a warning? */
480 break;
481 }
482
483 s->consecutive_errors = 0;
Olivier Houchard7059c552019-03-08 18:49:32 +0100484 _HA_ATOMIC_ADD(&s->counters.failed_hana, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100485
Simon Horman66183002013-02-23 10:16:43 +0900486 if (s->check.fastinter) {
487 expire = tick_add(now_ms, MS_TO_TICKS(s->check.fastinter));
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300488 if (s->check.task->expire > expire) {
Willy Tarreau5b3a2022012-09-28 15:01:02 +0200489 s->check.task->expire = expire;
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300490 /* requeue check task with new expire */
491 task_queue(s->check.task);
492 }
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100493 }
494}
495
Willy Tarreaua1dab552014-04-14 15:04:54 +0200496static int httpchk_build_status_header(struct server *s, char *buffer, int size)
Willy Tarreauef781042010-01-27 11:53:01 +0100497{
498 int sv_state;
499 int ratio;
500 int hlen = 0;
Joseph Lynch514061c2015-01-15 17:52:59 -0800501 char addr[46];
502 char port[6];
Willy Tarreauef781042010-01-27 11:53:01 +0100503 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
504 "UP %d/%d", "UP",
505 "NOLB %d/%d", "NOLB",
506 "no check" };
507
508 memcpy(buffer + hlen, "X-Haproxy-Server-State: ", 24);
509 hlen += 24;
510
Willy Tarreauff5ae352013-12-11 20:36:34 +0100511 if (!(s->check.state & CHK_ST_ENABLED))
512 sv_state = 6;
Emeric Brun52a91d32017-08-31 14:41:55 +0200513 else if (s->cur_state != SRV_ST_STOPPED) {
Simon Horman58c32972013-11-25 10:46:38 +0900514 if (s->check.health == s->check.rise + s->check.fall - 1)
Willy Tarreauef781042010-01-27 11:53:01 +0100515 sv_state = 3; /* UP */
516 else
517 sv_state = 2; /* going down */
518
Emeric Brun52a91d32017-08-31 14:41:55 +0200519 if (s->cur_state == SRV_ST_STOPPING)
Willy Tarreauef781042010-01-27 11:53:01 +0100520 sv_state += 2;
521 } else {
Simon Horman125d0992013-02-24 17:23:38 +0900522 if (s->check.health)
Willy Tarreauef781042010-01-27 11:53:01 +0100523 sv_state = 1; /* going up */
524 else
525 sv_state = 0; /* DOWN */
526 }
527
Willy Tarreaua1dab552014-04-14 15:04:54 +0200528 hlen += snprintf(buffer + hlen, size - hlen,
Willy Tarreauef781042010-01-27 11:53:01 +0100529 srv_hlt_st[sv_state],
Emeric Brun52a91d32017-08-31 14:41:55 +0200530 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
531 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreauef781042010-01-27 11:53:01 +0100532
Joseph Lynch514061c2015-01-15 17:52:59 -0800533 addr_to_str(&s->addr, addr, sizeof(addr));
Willy Tarreau04276f32017-01-06 17:41:29 +0100534 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
535 snprintf(port, sizeof(port), "%u", s->svc_port);
536 else
537 *port = 0;
Joseph Lynch514061c2015-01-15 17:52:59 -0800538
539 hlen += snprintf(buffer + hlen, size - hlen, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
540 addr, port, s->proxy->id, s->id,
Willy Tarreauef781042010-01-27 11:53:01 +0100541 global.node,
Emeric Brun52a91d32017-08-31 14:41:55 +0200542 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
Willy Tarreauef781042010-01-27 11:53:01 +0100543 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
544 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
545 s->nbpend);
546
Emeric Brun52a91d32017-08-31 14:41:55 +0200547 if ((s->cur_state == SRV_ST_STARTING) &&
Willy Tarreauef781042010-01-27 11:53:01 +0100548 now.tv_sec < s->last_change + s->slowstart &&
549 now.tv_sec >= s->last_change) {
550 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
Willy Tarreaua1dab552014-04-14 15:04:54 +0200551 hlen += snprintf(buffer + hlen, size - hlen, "; throttle=%d%%", ratio);
Willy Tarreauef781042010-01-27 11:53:01 +0100552 }
553
554 buffer[hlen++] = '\r';
555 buffer[hlen++] = '\n';
556
557 return hlen;
558}
559
Willy Tarreau20a18342013-12-05 00:31:46 +0100560/* Check the connection. If an error has already been reported or the socket is
561 * closed, keep errno intact as it is supposed to contain the valid error code.
562 * If no error is reported, check the socket's error queue using getsockopt().
563 * Warning, this must be done only once when returning from poll, and never
564 * after an I/O error was attempted, otherwise the error queue might contain
565 * inconsistent errors. If an error is detected, the CO_FL_ERROR is set on the
566 * socket. Returns non-zero if an error was reported, zero if everything is
567 * clean (including a properly closed socket).
568 */
569static int retrieve_errno_from_socket(struct connection *conn)
570{
571 int skerr;
572 socklen_t lskerr = sizeof(skerr);
573
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100574 if (conn->flags & CO_FL_ERROR && (unclean_errno(errno) || !conn->ctrl))
Willy Tarreau20a18342013-12-05 00:31:46 +0100575 return 1;
576
Willy Tarreau3c728722014-01-23 13:50:42 +0100577 if (!conn_ctrl_ready(conn))
Willy Tarreau20a18342013-12-05 00:31:46 +0100578 return 0;
579
Willy Tarreau585744b2017-08-24 14:31:19 +0200580 if (getsockopt(conn->handle.fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == 0)
Willy Tarreau20a18342013-12-05 00:31:46 +0100581 errno = skerr;
582
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100583 errno = unclean_errno(errno);
Willy Tarreau20a18342013-12-05 00:31:46 +0100584
585 if (!errno) {
586 /* we could not retrieve an error, that does not mean there is
587 * none. Just don't change anything and only report the prior
588 * error if any.
589 */
590 if (conn->flags & CO_FL_ERROR)
591 return 1;
592 else
593 return 0;
594 }
595
596 conn->flags |= CO_FL_ERROR | CO_FL_SOCK_WR_SH | CO_FL_SOCK_RD_SH;
597 return 1;
598}
599
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100600/* Try to collect as much information as possible on the connection status,
601 * and adjust the server status accordingly. It may make use of <errno_bck>
602 * if non-null when the caller is absolutely certain of its validity (eg:
603 * checked just after a syscall). If the caller doesn't have a valid errno,
604 * it can pass zero, and retrieve_errno_from_socket() will be called to try
605 * to extract errno from the socket. If no error is reported, it will consider
606 * the <expired> flag. This is intended to be used when a connection error was
607 * reported in conn->flags or when a timeout was reported in <expired>. The
608 * function takes care of not updating a server status which was already set.
609 * All situations where at least one of <expired> or CO_FL_ERROR are set
610 * produce a status.
611 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200612static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100613{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200614 struct conn_stream *cs = check->cs;
615 struct connection *conn = cs_conn(cs);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100616 const char *err_msg;
Willy Tarreau83061a82018-07-13 11:56:34 +0200617 struct buffer *chk;
Willy Tarreau213c6782014-10-02 14:51:02 +0200618 int step;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100619
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100620 if (check->result != CHK_RES_UNKNOWN)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100621 return;
622
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100623 errno = unclean_errno(errno_bck);
624 if (conn && errno)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100625 retrieve_errno_from_socket(conn);
626
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200627 if (conn && !(conn->flags & CO_FL_ERROR) &&
628 !(cs->flags & CS_FL_ERROR) && !expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100629 return;
630
631 /* we'll try to build a meaningful error message depending on the
632 * context of the error possibly present in conn->err_code, and the
633 * socket error possibly collected above. This is useful to know the
634 * exact step of the L6 layer (eg: SSL handshake).
635 */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200636 chk = get_trash_chunk();
637
Christopher Faulet799f3a42020-04-07 12:06:14 +0200638 if (check->type == PR_O2_TCPCHK_CHK &&
639 !(check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200640 step = tcpcheck_get_step_id(check, NULL);
Willy Tarreau213c6782014-10-02 14:51:02 +0200641 if (!step)
642 chunk_printf(chk, " at initial connection step of tcp-check");
643 else {
644 chunk_printf(chk, " at step %d of tcp-check", step);
645 /* we were looking for a string */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200646 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
647 if (check->current_step->connect.port)
648 chunk_appendf(chk, " (connect port %d)" ,check->current_step->connect.port);
Willy Tarreau213c6782014-10-02 14:51:02 +0200649 else
650 chunk_appendf(chk, " (connect)");
651 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200652 else if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
653 struct tcpcheck_expect *expect = &check->current_step->expect;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100654
655 switch (expect->type) {
656 case TCPCHK_EXPECT_STRING:
657 chunk_appendf(chk, " (expect string '%s')", expect->string);
658 break;
659 case TCPCHK_EXPECT_BINARY:
660 chunk_appendf(chk, " (expect binary '%s')", expect->string);
661 break;
662 case TCPCHK_EXPECT_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200663 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100664 break;
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100665 case TCPCHK_EXPECT_REGEX_BINARY:
666 chunk_appendf(chk, " (expect binary regex)");
667 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200668 case TCPCHK_EXPECT_CUSTOM:
669 chunk_appendf(chk, " (expect custom function)");
670 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100671 case TCPCHK_EXPECT_UNDEF:
672 chunk_appendf(chk, " (undefined expect!)");
673 break;
674 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200675 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200676 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200677 chunk_appendf(chk, " (send)");
678 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200679
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200680 if (check->current_step && check->current_step->comment)
681 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200682 }
683 }
684
Willy Tarreau00149122017-10-04 18:05:01 +0200685 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100686 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200687 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
688 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100689 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200690 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
691 chk->area);
692 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100693 }
694 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100695 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200696 chunk_printf(&trash, "%s%s", strerror(errno),
697 chk->area);
698 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100699 }
700 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200701 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100702 }
703 }
704
Willy Tarreau00149122017-10-04 18:05:01 +0200705 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200706 /* NOTE: this is reported after <fall> tries */
707 chunk_printf(chk, "No port available for the TCP connection");
708 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
709 }
710
Willy Tarreau00149122017-10-04 18:05:01 +0200711 if (!conn) {
712 /* connection allocation error before the connection was established */
713 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
714 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100715 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100716 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200717 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100718 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
719 else if (expired)
720 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200721
722 /*
723 * might be due to a server IP change.
724 * Let's trigger a DNS resolution if none are currently running.
725 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100726 if (check->server)
727 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200728
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100729 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100730 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100731 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200732 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100733 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
734 else if (expired)
735 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
736 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200737 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100738 /* I/O error after connection was established and before we could diagnose */
739 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
740 }
741 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200742 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
743
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100744 /* connection established but expired check */
Christopher Faulet811f78c2020-04-01 11:10:27 +0200745 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
746 tout = check->current_step->expect.tout_status;
747 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100748 }
749
750 return;
751}
752
Olivier Houchard5c110b92018-08-14 17:04:58 +0200753/* This function checks if any I/O is wanted, and if so, attempts to do so */
754static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200755{
Olivier Houchard26e1a8f2018-09-12 15:15:12 +0200756 struct check *check = ctx;
757 struct conn_stream *cs = check->cs;
Olivier Houchard0923fa42019-01-11 18:43:04 +0100758 struct email_alertq *q = container_of(check, typeof(*q), check);
Olivier Houchardbc89ad82019-07-09 17:28:51 +0200759 int ret = 0;
Olivier Houchard4501c3e2018-08-28 19:36:18 +0200760
Willy Tarreau4f6516d2018-12-19 13:59:17 +0100761 if (!(check->wait_list.events & SUB_RETRY_SEND))
Olivier Houchardbc89ad82019-07-09 17:28:51 +0200762 ret = wake_srv_chk(cs);
763 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
Olivier Houchard0923fa42019-01-11 18:43:04 +0100764 if (check->server)
765 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
766 else
767 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200768 __event_srv_chk_r(cs);
Olivier Houchard0923fa42019-01-11 18:43:04 +0100769 if (check->server)
770 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
771 else
772 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200773 }
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200774 return NULL;
775}
776
777/* same as above but protected by the server lock.
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100778 *
779 * Please do NOT place any return statement in this function and only leave
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200780 * via the out label. NOTE THAT THIS FUNCTION DOESN'T LOCK, YOU PROBABLY WANT
781 * TO USE event_srv_chk_w() instead.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200782 */
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200783static void __event_srv_chk_w(struct conn_stream *cs)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200784{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200785 struct connection *conn = cs->conn;
786 struct check *check = cs->data;
Simon Horman4a741432013-02-23 15:35:38 +0900787 struct server *s = check->server;
Simon Horman4a741432013-02-23 15:35:38 +0900788 struct task *t = check->task;
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200789
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100790 if (unlikely(check->result == CHK_RES_FAILED))
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100791 goto out_wakeup;
792
Willy Tarreau20a18342013-12-05 00:31:46 +0100793 if (retrieve_errno_from_socket(conn)) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200794 chk_report_conn_err(check, errno, 0);
Willy Tarreau20a18342013-12-05 00:31:46 +0100795 goto out_wakeup;
796 }
Krzysztof Piotr Oledzki6492db52010-01-02 22:03:01 +0100797
Willy Tarreau06559ac2013-12-05 01:53:08 +0100798 /* here, we know that the connection is established. That's enough for
799 * a pure TCP check.
800 */
801 if (!check->type)
802 goto out_wakeup;
803
Willy Tarreauc09572f2017-10-04 11:58:22 +0200804 /* wake() will take care of calling tcpcheck_main() */
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100805 if (check->type == PR_O2_TCPCHK_CHK)
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200806 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200807
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200808 if (b_data(&check->bo)) {
Olivier Houcharded0f2072018-08-16 15:41:52 +0200809 cs->conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200810 b_realign_if_empty(&check->bo);
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200811 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200812 chk_report_conn_err(check, errno, 0);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100813 goto out_wakeup;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200814 }
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200815 if (b_data(&check->bo)) {
Willy Tarreau4f6516d2018-12-19 13:59:17 +0100816 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200817 goto out;
818 }
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100819 }
Willy Tarreau6996e152007-04-30 14:37:43 +0200820
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100821 /* full request sent, we allow up to <timeout.check> if nonzero for a response */
822 if (s->proxy->timeout.check) {
823 t->expire = tick_add_ifset(now_ms, s->proxy->timeout.check);
824 task_queue(t);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200825 }
Olivier Houchard53216e72018-10-10 15:46:36 +0200826 goto out;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100827
Willy Tarreau83749182007-04-15 20:56:27 +0200828 out_wakeup:
Willy Tarreaufdccded2008-08-29 18:19:04 +0200829 task_wakeup(t, TASK_WOKEN_IO);
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200830 out:
831 return;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200832}
833
Willy Tarreaubaaee002006-06-26 02:48:02 +0200834/*
Willy Tarreauf3c69202006-07-09 16:42:34 +0200835 * This function is used only for server health-checks. It handles the server's
Hervé COMMOWICK8776f1b2010-10-18 15:58:36 +0200836 * reply to an HTTP request, SSL HELLO or MySQL client Auth. It calls
Simon Horman4a741432013-02-23 15:35:38 +0900837 * set_server_check_status() to update check->status, check->duration
838 * and check->result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200839
840 * The set_server_check_status function is called with HCHK_STATUS_L7OKD if
841 * an HTTP server replies HTTP 2xx or 3xx (valid responses), if an SMTP server
842 * returns 2xx, HCHK_STATUS_L6OK if an SSL server returns at least 5 bytes in
843 * response to an SSL HELLO (the principle is that this is enough to
844 * distinguish between an SSL server and a pure TCP relay). All other cases will
845 * call it with a proper error status like HCHK_STATUS_L7STS, HCHK_STATUS_L6RSP,
846 * etc.
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100847 *
848 * Please do NOT place any return statement in this function and only leave
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200849 * via the out label.
850 *
851 * This must be called with the server lock held.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200852 */
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200853static void __event_srv_chk_r(struct conn_stream *cs)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200854{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200855 struct connection *conn = cs->conn;
856 struct check *check = cs->data;
Simon Horman4a741432013-02-23 15:35:38 +0900857 struct server *s = check->server;
858 struct task *t = check->task;
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200859 char *desc;
Willy Tarreau03938182010-03-17 21:52:07 +0100860 int done;
Willy Tarreau83749182007-04-15 20:56:27 +0200861
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100862 if (unlikely(check->result == CHK_RES_FAILED))
Willy Tarreau83749182007-04-15 20:56:27 +0200863 goto out_wakeup;
Willy Tarreau83749182007-04-15 20:56:27 +0200864
Willy Tarreauc09572f2017-10-04 11:58:22 +0200865 /* wake() will take care of calling tcpcheck_main() */
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100866 if (check->type == PR_O2_TCPCHK_CHK)
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200867 goto out;
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200868
Willy Tarreau83749182007-04-15 20:56:27 +0200869 /* Warning! Linux returns EAGAIN on SO_ERROR if data are still available
870 * but the connection was closed on the remote end. Fortunately, recv still
871 * works correctly and we don't need to do the getsockopt() on linux.
872 */
Nick Chalk57b1bf72010-03-16 15:50:46 +0000873
874 /* Set buffer to point to the end of the data already read, and check
875 * that there is free space remaining. If the buffer is full, proceed
876 * with running the checks without attempting another socket read.
877 */
Nick Chalk57b1bf72010-03-16 15:50:46 +0000878
Willy Tarreau03938182010-03-17 21:52:07 +0100879 done = 0;
Nick Chalk57b1bf72010-03-16 15:50:46 +0000880
Olivier Houchard511efea2018-08-16 15:30:32 +0200881 cs->conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0);
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200882 if (conn->flags & (CO_FL_ERROR | CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
Willy Tarreau03938182010-03-17 21:52:07 +0100883 done = 1;
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200884 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
Willy Tarreauf1503172012-09-28 19:39:36 +0200885 /* Report network errors only if we got no other data. Otherwise
886 * we'll let the upper layers decide whether the response is OK
887 * or not. It is very common that an RST sent by the server is
888 * reported as an error just after the last data chunk.
889 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200890 chk_report_conn_err(check, errno, 0);
Willy Tarreauc1a07962010-03-16 20:55:43 +0100891 goto out_wakeup;
892 }
Willy Tarreaubaaee002006-06-26 02:48:02 +0200893 }
894
Willy Tarreau0f0393f2019-09-24 10:43:03 +0200895 /* the rest of the code below expects the connection to be ready! */
Willy Tarreau911db9b2020-01-23 16:27:54 +0100896 if (conn->flags & CO_FL_WAIT_XPRT && !done)
Willy Tarreau0f0393f2019-09-24 10:43:03 +0200897 goto wait_more_data;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100898
Willy Tarreau03938182010-03-17 21:52:07 +0100899 /* Intermediate or complete response received.
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200900 * Terminate string in b_head(&check->bi) buffer.
Willy Tarreau03938182010-03-17 21:52:07 +0100901 */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200902 if (b_data(&check->bi) < b_size(&check->bi))
903 b_head(&check->bi)[b_data(&check->bi)] = '\0';
Willy Tarreau03938182010-03-17 21:52:07 +0100904 else {
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200905 b_head(&check->bi)[b_data(&check->bi) - 1] = '\0';
Willy Tarreau03938182010-03-17 21:52:07 +0100906 done = 1; /* buffer full, don't wait for more data */
907 }
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200908
Nick Chalk57b1bf72010-03-16 15:50:46 +0000909 /* Run the checks... */
Simon Horman4a741432013-02-23 15:35:38 +0900910 switch (check->type) {
Willy Tarreau1620ec32011-08-06 17:05:02 +0200911 case PR_O2_HTTP_CHK:
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200912 if (!done && b_data(&check->bi) < strlen("HTTP/1.0 000\r"))
Willy Tarreau03938182010-03-17 21:52:07 +0100913 goto wait_more_data;
914
Willy Tarreauc7dd71a2007-11-30 08:33:21 +0100915 /* Check if the server speaks HTTP 1.X */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200916 if ((b_data(&check->bi) < strlen("HTTP/1.0 000\r")) ||
917 (memcmp(b_head(&check->bi), "HTTP/1.", 7) != 0 ||
918 (*(b_head(&check->bi) + 12) != ' ' && *(b_head(&check->bi) + 12) != '\r')) ||
919 !isdigit((unsigned char) *(b_head(&check->bi) + 9)) || !isdigit((unsigned char) *(b_head(&check->bi) + 10)) ||
920 !isdigit((unsigned char) *(b_head(&check->bi) + 11))) {
921 cut_crlf(b_head(&check->bi));
922 set_server_check_status(check, HCHK_STATUS_L7RSP, b_head(&check->bi));
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200923
Willy Tarreauc7dd71a2007-11-30 08:33:21 +0100924 goto out_wakeup;
925 }
926
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200927 check->code = str2uic(b_head(&check->bi) + 9);
928 desc = ltrim(b_head(&check->bi) + 12, ' ');
Christopher Fauletc2a89a62017-10-23 15:54:24 +0200929
Willy Tarreaubd741542010-03-16 18:46:54 +0100930 if ((s->proxy->options & PR_O_DISABLE404) &&
Emeric Brun52a91d32017-08-31 14:41:55 +0200931 (s->next_state != SRV_ST_STOPPED) && (check->code == 404)) {
Nick Chalk57b1bf72010-03-16 15:50:46 +0000932 /* 404 may be accepted as "stopping" only if the server was up */
933 cut_crlf(desc);
Simon Horman4a741432013-02-23 15:35:38 +0900934 set_server_check_status(check, HCHK_STATUS_L7OKCD, desc);
Nick Chalk57b1bf72010-03-16 15:50:46 +0000935 }
Willy Tarreaubd741542010-03-16 18:46:54 +0100936 else if (s->proxy->options2 & PR_O2_EXP_TYPE) {
937 /* Run content verification check... We know we have at least 13 chars */
938 if (!httpchk_expect(s, done))
939 goto wait_more_data;
940 }
941 /* check the reply : HTTP/1.X 2xx and 3xx are OK */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200942 else if (*(b_head(&check->bi) + 9) == '2' || *(b_head(&check->bi) + 9) == '3') {
Willy Tarreaubd741542010-03-16 18:46:54 +0100943 cut_crlf(desc);
Simon Horman4a741432013-02-23 15:35:38 +0900944 set_server_check_status(check, HCHK_STATUS_L7OKD, desc);
Willy Tarreaubd741542010-03-16 18:46:54 +0100945 }
Nick Chalk57b1bf72010-03-16 15:50:46 +0000946 else {
947 cut_crlf(desc);
Simon Horman4a741432013-02-23 15:35:38 +0900948 set_server_check_status(check, HCHK_STATUS_L7STS, desc);
Nick Chalk57b1bf72010-03-16 15:50:46 +0000949 }
Willy Tarreau1620ec32011-08-06 17:05:02 +0200950 break;
951
Willy Tarreau1620ec32011-08-06 17:05:02 +0200952 default:
Willy Tarreau4c1a2b32019-09-05 18:43:22 +0200953 /* good connection is enough for pure TCP check */
Willy Tarreau911db9b2020-01-23 16:27:54 +0100954 if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Christopher Fauletf61f33a2020-03-27 18:55:49 +0100955 if (check->use_ssl == 1)
Willy Tarreau4c1a2b32019-09-05 18:43:22 +0200956 set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
957 else
958 set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
959 }
Willy Tarreau1620ec32011-08-06 17:05:02 +0200960 break;
961 } /* switch */
Willy Tarreau83749182007-04-15 20:56:27 +0200962
Willy Tarreauc7dd71a2007-11-30 08:33:21 +0100963 out_wakeup:
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100964 /* collect possible new errors */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200965 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200966 chk_report_conn_err(check, 0, 0);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200967
Nick Chalk57b1bf72010-03-16 15:50:46 +0000968 /* Reset the check buffer... */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200969 *b_head(&check->bi) = '\0';
970 b_reset(&check->bi);
Nick Chalk57b1bf72010-03-16 15:50:46 +0000971
Steven Davidovitz544d4812017-03-08 11:06:20 -0800972 /* Close the connection... We still attempt to nicely close if,
973 * for instance, SSL needs to send a "close notify." Later, we perform
974 * a hard close and reset the connection if some data are pending,
975 * otherwise we end up with many TIME_WAITs and eat all the source port
976 * range quickly. To avoid sending RSTs all the time, we first try to
977 * drain pending data.
Willy Tarreaufd29cc52012-11-23 09:18:20 +0100978 */
Olivier Houchard6c7e96a2019-07-02 16:35:18 +0200979 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
980 * connection, to make sure cs_shutw() will not lead to a shutdown()
981 * that would provoke TIME_WAITs.
982 */
983 cs_shutr(cs, CS_SHR_DRAIN);
Willy Tarreauecdb3fe2017-10-05 15:25:48 +0200984 cs_shutw(cs, CS_SHW_NORMAL);
Willy Tarreau2b57cb82013-06-10 19:56:38 +0200985
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100986 /* OK, let's not stay here forever */
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100987 if (check->result == CHK_RES_FAILED)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100988 conn->flags |= CO_FL_ERROR;
989
Willy Tarreaufdccded2008-08-29 18:19:04 +0200990 task_wakeup(t, TASK_WOKEN_IO);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200991out:
Willy Tarreau3267d362012-08-17 23:53:56 +0200992 return;
Willy Tarreau03938182010-03-17 21:52:07 +0100993
994 wait_more_data:
Willy Tarreau4f6516d2018-12-19 13:59:17 +0100995 cs->conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200996 goto out;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200997}
998
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200999/*
1000 * This function is used only for server health-checks. It handles connection
1001 * status updates including errors. If necessary, it wakes the check task up.
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001002 * It returns 0 on normal cases, <0 if at least one close() has happened on the
1003 * connection (eg: reconnect).
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001004 */
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001005static int wake_srv_chk(struct conn_stream *cs)
Willy Tarreau20bea422012-07-06 12:00:49 +02001006{
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001007 struct connection *conn = cs->conn;
1008 struct check *check = cs->data;
Olivier Houchard0923fa42019-01-11 18:43:04 +01001009 struct email_alertq *q = container_of(check, typeof(*q), check);
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001010 int ret = 0;
Willy Tarreau20bea422012-07-06 12:00:49 +02001011
Olivier Houchard0923fa42019-01-11 18:43:04 +01001012 if (check->server)
1013 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
1014 else
1015 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Emeric Brunf6ba17d2017-11-02 14:35:27 +01001016
Willy Tarreauc09572f2017-10-04 11:58:22 +02001017 /* we may have to make progress on the TCP checks */
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001018 if (check->type == PR_O2_TCPCHK_CHK) {
1019 ret = tcpcheck_main(check);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001020 cs = check->cs;
Willy Tarreau543abd42018-09-20 11:25:12 +02001021 conn = cs->conn;
Willy Tarreauc5940392019-09-05 17:38:40 +02001022 } else {
1023 if (!(check->wait_list.events & SUB_RETRY_SEND))
1024 __event_srv_chk_w(cs);
1025 if (!(check->wait_list.events & SUB_RETRY_RECV))
1026 __event_srv_chk_r(cs);
1027 }
Willy Tarreauc09572f2017-10-04 11:58:22 +02001028
Willy Tarreau4ff3b892017-10-16 15:17:17 +02001029 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
Willy Tarreau02b0f582013-12-03 15:42:33 +01001030 /* We may get error reports bypassing the I/O handlers, typically
1031 * the case when sending a pure TCP check which fails, then the I/O
1032 * handlers above are not called. This is completely handled by the
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001033 * main processing task so let's simply wake it up. If we get here,
1034 * we expect errno to still be valid.
1035 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001036 chk_report_conn_err(check, errno, 0);
Willy Tarreau2d351b62013-12-05 02:36:25 +01001037 task_wakeup(check->task, TASK_WOKEN_IO);
1038 }
Willy Tarreau911db9b2020-01-23 16:27:54 +01001039 else if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Willy Tarreau3be293f2014-02-05 18:31:24 +01001040 /* we may get here if only a connection probe was required : we
1041 * don't have any data to send nor anything expected in response,
1042 * so the completion of the connection establishment is enough.
1043 */
1044 task_wakeup(check->task, TASK_WOKEN_IO);
1045 }
Willy Tarreau2d351b62013-12-05 02:36:25 +01001046
Willy Tarreau6aaa1b82013-12-11 17:09:34 +01001047 if (check->result != CHK_RES_UNKNOWN) {
Christopher Faulet774c4862019-01-21 14:15:50 +01001048 /* Check complete or aborted. If connection not yet closed do it
1049 * now and wake the check task up to be sure the result is
1050 * handled ASAP. */
Willy Tarreaud85c4852015-03-13 00:40:28 +01001051 conn_sock_drain(conn);
Willy Tarreaua553ae92017-10-05 18:52:17 +02001052 cs_close(cs);
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001053 ret = -1;
Olivier Houchardf4949572019-07-02 17:42:22 +02001054 /* We may have been scheduled to run, and the
1055 * I/O handler expects to have a cs, so remove
1056 * the tasklet
1057 */
1058 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Christopher Faulet774c4862019-01-21 14:15:50 +01001059 task_wakeup(check->task, TASK_WOKEN_IO);
Willy Tarreau2d351b62013-12-05 02:36:25 +01001060 }
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001061
Olivier Houchard0923fa42019-01-11 18:43:04 +01001062 if (check->server)
1063 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
1064 else
1065 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Emeric Brunf6ba17d2017-11-02 14:35:27 +01001066
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001067 /* if a connection got replaced, we must absolutely prevent the connection
1068 * handler from touching its fd, and perform the FD polling updates ourselves
1069 */
1070 if (ret < 0)
1071 conn_cond_update_polling(conn);
1072
1073 return ret;
Willy Tarreau20bea422012-07-06 12:00:49 +02001074}
1075
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001076struct data_cb check_conn_cb = {
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001077 .wake = wake_srv_chk,
Willy Tarreau8e0bb0a2016-11-24 16:58:12 +01001078 .name = "CHCK",
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001079};
1080
Willy Tarreaubaaee002006-06-26 02:48:02 +02001081/*
Willy Tarreau2e993902011-10-31 11:53:20 +01001082 * updates the server's weight during a warmup stage. Once the final weight is
1083 * reached, the task automatically stops. Note that any server status change
1084 * must have updated s->last_change accordingly.
1085 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001086static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Willy Tarreau2e993902011-10-31 11:53:20 +01001087{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001088 struct server *s = context;
Willy Tarreau2e993902011-10-31 11:53:20 +01001089
1090 /* by default, plan on stopping the task */
1091 t->expire = TICK_ETERNITY;
Emeric Brun52a91d32017-08-31 14:41:55 +02001092 if ((s->next_admin & SRV_ADMF_MAINT) ||
1093 (s->next_state != SRV_ST_STARTING))
Willy Tarreau2e993902011-10-31 11:53:20 +01001094 return t;
1095
Willy Tarreau4fc49a92019-05-05 06:54:22 +02001096 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
1097
Willy Tarreau892337c2014-05-13 23:41:20 +02001098 /* recalculate the weights and update the state */
Willy Tarreau3ff577e2018-08-02 11:48:52 +02001099 server_recalc_eweight(s, 1);
Willy Tarreau2e993902011-10-31 11:53:20 +01001100
1101 /* probably that we can refill this server with a bit more connections */
Willy Tarreau4aac7db2014-05-16 11:48:10 +02001102 pendconn_grab_from_px(s);
Willy Tarreau2e993902011-10-31 11:53:20 +01001103
Willy Tarreau4fc49a92019-05-05 06:54:22 +02001104 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
1105
Willy Tarreau2e993902011-10-31 11:53:20 +01001106 /* get back there in 1 second or 1/20th of the slowstart interval,
1107 * whichever is greater, resulting in small 5% steps.
1108 */
Emeric Brun52a91d32017-08-31 14:41:55 +02001109 if (s->next_state == SRV_ST_STARTING)
Willy Tarreau2e993902011-10-31 11:53:20 +01001110 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
1111 return t;
1112}
1113
Willy Tarreau894c6422017-10-04 15:58:52 +02001114/* returns the first NON-COMMENT tcp-check rule from list <list> or NULL if
1115 * none was found.
1116 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001117static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Willy Tarreau894c6422017-10-04 15:58:52 +02001118{
1119 struct tcpcheck_rule *r;
1120
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001121 list_for_each_entry(r, rules->list, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01001122 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
Willy Tarreau894c6422017-10-04 15:58:52 +02001123 return r;
1124 }
1125 return NULL;
1126}
1127
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001128/* returns the NON-COMMENT tcp-check rule from list <list> following <start> or
1129 * NULL if non was found. If <start> is NULL, it relies on
1130 * get_first_tcpcheck_rule().
1131 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001132static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001133{
1134 struct tcpcheck_rule *r;
1135
1136 if (!start)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001137 return get_first_tcpcheck_rule(rules);
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001138
1139 r = LIST_NEXT(&start->list, typeof(r), list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001140 list_for_each_entry_from(r, rules->list, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01001141 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001142 return r;
1143 }
1144 return NULL;
1145}
1146
Willy Tarreau2e993902011-10-31 11:53:20 +01001147/*
Simon Horman98637e52014-06-20 12:30:16 +09001148 * establish a server health-check that makes use of a connection.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001149 *
1150 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001151 * - SF_ERR_NONE if everything's OK and tcpcheck_main() was not called
1152 * - SF_ERR_UP if if everything's OK and tcpcheck_main() was called
1153 * - SF_ERR_SRVTO if there are no more servers
1154 * - SF_ERR_SRVCL if the connection was refused by the server
1155 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1156 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1157 * - SF_ERR_INTERNAL for any other purely internal errors
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001158 * - SF_ERR_CHK_PORT if no port could be found to run a health check on an AF_INET* socket
Tim Düsterhus4896c442016-11-29 02:15:19 +01001159 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001160 * Note that we try to prevent the network stack from sending the ACK during the
1161 * connect() when a pure TCP check is used (without PROXY protocol).
1162 */
Simon Horman98637e52014-06-20 12:30:16 +09001163static int connect_conn_chk(struct task *t)
Simon Hormanb00d17a2014-06-13 16:18:16 +09001164{
1165 struct check *check = t->context;
1166 struct server *s = check->server;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001167 struct conn_stream *cs = check->cs;
1168 struct connection *conn = cs_conn(cs);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001169 struct protocol *proto;
1170 int ret;
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001171 int connflags = 0;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001172
Willy Tarreau00149122017-10-04 18:05:01 +02001173 /* we cannot have a connection here */
1174 if (conn)
1175 return SF_ERR_INTERNAL;
1176
Simon Hormanb00d17a2014-06-13 16:18:16 +09001177 /* prepare the check buffer.
1178 * This should not be used if check is the secondary agent check
1179 * of a server as s->proxy->check_req will relate to the
1180 * configuration of the primary check. Similarly, tcp-check uses
1181 * its own strings.
1182 */
1183 if (check->type && check->type != PR_O2_TCPCHK_CHK && !(check->state & CHK_ST_AGENT)) {
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001184 b_putblk(&check->bo, s->proxy->check_req, s->proxy->check_len);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001185
Christopher Faulet811f78c2020-04-01 11:10:27 +02001186 /* we want to check if this host replies to HTTP requests
Simon Hormanb00d17a2014-06-13 16:18:16 +09001187 * so we'll send the request, and won't wake the checker up now.
1188 */
Christopher Faulet811f78c2020-04-01 11:10:27 +02001189 if ((check->type) == PR_O2_HTTP_CHK) {
Cyril Bonté32602d22015-01-30 00:07:07 +01001190 /* prevent HTTP keep-alive when "http-check expect" is used */
1191 if (s->proxy->options2 & PR_O2_EXP_TYPE)
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001192 b_putist(&check->bo, ist("Connection: close\r\n"));
Christopher Faulet8acb1282020-04-09 08:44:06 +02001193
1194 /* If there is a body, add its content-length */
1195 if (s->proxy->check_body_len)
1196 chunk_appendf(&check->bo, "Content-Length: %s\r\n", ultoa(s->proxy->check_body_len));
1197
1198 /* Add configured headers */
1199 if (s->proxy->check_hdrs)
1200 b_putblk(&check->bo, s->proxy->check_hdrs, s->proxy->check_hdrs_len);
1201
1202 /* Add send-state header */
1203 if (s->proxy->options2 & PR_O2_CHK_SNDST)
1204 b_putblk(&check->bo, trash.area,
1205 httpchk_build_status_header(s, trash.area, trash.size));
1206
1207 /* end-of-header */
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001208 b_putist(&check->bo, ist("\r\n"));
Christopher Faulet8acb1282020-04-09 08:44:06 +02001209
1210 /* Add the body */
1211 if (s->proxy->check_body)
1212 b_putblk(&check->bo, s->proxy->check_body, s->proxy->check_body_len);
1213
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001214 *b_tail(&check->bo) = '\0'; /* to make gdb output easier to read */
Simon Hormanb00d17a2014-06-13 16:18:16 +09001215 }
1216 }
1217
Willy Tarreauf411cce2017-10-04 16:21:19 +02001218 /* for tcp-checks, the initial connection setup is handled separately as
1219 * it may be sent to a specific port and not to the server's.
1220 */
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001221 if (check->type == PR_O2_TCPCHK_CHK) {
1222 /* tcpcheck initialisation */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02001223 check->current_step = NULL;
Willy Tarreauf411cce2017-10-04 16:21:19 +02001224 tcpcheck_main(check);
1225 return SF_ERR_UP;
1226 }
1227
Simon Hormanb00d17a2014-06-13 16:18:16 +09001228 /* prepare a new connection */
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001229 cs = check->cs = cs_new(NULL);
1230 if (!check->cs)
Willy Tarreau00149122017-10-04 18:05:01 +02001231 return SF_ERR_RESOURCE;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001232 conn = cs->conn;
Olivier Houchard26e1a8f2018-09-12 15:15:12 +02001233 /* Maybe there were an older connection we were waiting on */
Willy Tarreau4f6516d2018-12-19 13:59:17 +01001234 check->wait_list.events = 0;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02001235 tasklet_set_tid(check->wait_list.tasklet, tid);
1236
Simon Hormanb00d17a2014-06-13 16:18:16 +09001237
Willy Tarreauca79f592019-07-17 19:04:47 +02001238 if (!sockaddr_alloc(&conn->dst))
1239 return SF_ERR_RESOURCE;
1240
Simon Horman41f58762015-01-30 11:22:56 +09001241 if (is_addr(&check->addr)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001242 /* we'll connect to the check addr specified on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001243 *conn->dst = check->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001244 }
1245 else {
1246 /* we'll connect to the addr on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001247 *conn->dst = s->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001248 }
1249
Alexander Liu2a54bb72019-05-22 19:44:48 +08001250 if (s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1251 conn->send_proxy_ofs = 1;
1252 conn->flags |= CO_FL_SOCKS4;
1253 }
1254
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001255 proto = protocol_by_family(conn->dst->ss_family);
Olivier Houchard6377a002017-12-01 22:04:05 +01001256 conn->target = &s->obj_type;
1257
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001258 if ((conn->dst->ss_family == AF_INET) || (conn->dst->ss_family == AF_INET6)) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001259 int i = 0;
1260
1261 i = srv_check_healthcheck_port(check);
Olivier Houchard6377a002017-12-01 22:04:05 +01001262 if (i == 0)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001263 return SF_ERR_CHK_PORT;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001264
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001265 set_host_port(conn->dst, i);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001266 }
1267
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001268 /* no client address */
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001269
Willy Tarreaube373152018-09-06 11:45:30 +02001270 conn_prepare(conn, proto, check->xprt);
Olivier Houchardf67be932019-01-29 15:47:43 +01001271 if (conn_install_mux(conn, &mux_pt_ops, cs, s->proxy, NULL) < 0)
1272 return SF_ERR_RESOURCE;
Willy Tarreaube373152018-09-06 11:45:30 +02001273 cs_attach(cs, check, &check_conn_cb);
1274
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001275 /* only plain tcp check supports quick ACK */
1276 connflags |= (check->type ? CONNECT_HAS_DATA : CONNECT_DELACK_ALWAYS);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001277
Willy Tarreaue7dff022015-04-03 01:14:29 +02001278 ret = SF_ERR_INTERNAL;
Olivier Houchardb68fda42017-08-04 18:39:01 +02001279 if (proto && proto->connect)
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001280 ret = proto->connect(conn, connflags);
Willy Tarreau16257f62017-11-02 15:45:00 +01001281
Willy Tarreau16257f62017-11-02 15:45:00 +01001282
Olivier Houchard9130a962017-10-17 17:33:43 +02001283#ifdef USE_OPENSSL
Olivier Houcharda48437b2019-01-29 16:37:52 +01001284 if (ret == SF_ERR_NONE) {
1285 if (s->check.sni)
1286 ssl_sock_set_servername(conn, s->check.sni);
1287 if (s->check.alpn_str)
1288 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str,
1289 s->check.alpn_len);
1290 }
Olivier Houchard9130a962017-10-17 17:33:43 +02001291#endif
Willy Tarreauf4949772017-05-06 08:45:28 +02001292 if (s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001293 conn->send_proxy_ofs = 1;
1294 conn->flags |= CO_FL_SEND_PROXY;
Olivier Houchard37d78972019-12-30 15:13:42 +01001295 }
1296 if (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4) &&
1297 conn_ctrl_ready(conn)) {
Olivier Houchardfe50bfb2019-05-27 12:09:19 +02001298 if (xprt_add_hs(conn) < 0)
1299 ret = SF_ERR_RESOURCE;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001300 }
1301
1302 return ret;
1303}
1304
Simon Horman98637e52014-06-20 12:30:16 +09001305static struct list pid_list = LIST_HEAD_INIT(pid_list);
Willy Tarreaubafbe012017-11-24 17:34:44 +01001306static struct pool_head *pool_head_pid_list;
Willy Tarreau86abe442018-11-25 20:12:18 +01001307__decl_spinlock(pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001308
1309void block_sigchld(void)
1310{
1311 sigset_t set;
1312 sigemptyset(&set);
1313 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001314 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001315}
1316
1317void unblock_sigchld(void)
1318{
1319 sigset_t set;
1320 sigemptyset(&set);
Willy Tarreauebc92442016-06-21 17:29:46 +02001321 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001322 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001323}
1324
Simon Horman98637e52014-06-20 12:30:16 +09001325static struct pid_list *pid_list_add(pid_t pid, struct task *t)
1326{
1327 struct pid_list *elem;
1328 struct check *check = t->context;
1329
Willy Tarreaubafbe012017-11-24 17:34:44 +01001330 elem = pool_alloc(pool_head_pid_list);
Simon Horman98637e52014-06-20 12:30:16 +09001331 if (!elem)
1332 return NULL;
1333 elem->pid = pid;
1334 elem->t = t;
1335 elem->exited = 0;
1336 check->curpid = elem;
1337 LIST_INIT(&elem->list);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001338
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001339 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001340 LIST_ADD(&pid_list, &elem->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001341 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001342
Simon Horman98637e52014-06-20 12:30:16 +09001343 return elem;
1344}
1345
Simon Horman98637e52014-06-20 12:30:16 +09001346static void pid_list_del(struct pid_list *elem)
1347{
1348 struct check *check;
1349
1350 if (!elem)
1351 return;
1352
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001353 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001354 LIST_DEL(&elem->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001355 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001356
Simon Horman98637e52014-06-20 12:30:16 +09001357 if (!elem->exited)
1358 kill(elem->pid, SIGTERM);
1359
1360 check = elem->t->context;
1361 check->curpid = NULL;
Willy Tarreaubafbe012017-11-24 17:34:44 +01001362 pool_free(pool_head_pid_list, elem);
Simon Horman98637e52014-06-20 12:30:16 +09001363}
1364
1365/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
1366static void pid_list_expire(pid_t pid, int status)
1367{
1368 struct pid_list *elem;
1369
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001370 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001371 list_for_each_entry(elem, &pid_list, list) {
1372 if (elem->pid == pid) {
1373 elem->t->expire = now_ms;
1374 elem->status = status;
1375 elem->exited = 1;
Cyril Bonté9dbcfab2014-08-07 01:55:39 +02001376 task_wakeup(elem->t, TASK_WOKEN_IO);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001377 break;
Simon Horman98637e52014-06-20 12:30:16 +09001378 }
1379 }
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001380 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001381}
1382
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001383static void sigchld_handler(struct sig_handler *sh)
Simon Horman98637e52014-06-20 12:30:16 +09001384{
1385 pid_t pid;
1386 int status;
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001387
Simon Horman98637e52014-06-20 12:30:16 +09001388 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
1389 pid_list_expire(pid, status);
1390}
1391
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001392static int init_pid_list(void)
1393{
Willy Tarreaubafbe012017-11-24 17:34:44 +01001394 if (pool_head_pid_list != NULL)
Simon Horman98637e52014-06-20 12:30:16 +09001395 /* Nothing to do */
1396 return 0;
1397
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001398 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001399 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
1400 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001401 return 1;
1402 }
1403
Willy Tarreaubafbe012017-11-24 17:34:44 +01001404 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
1405 if (pool_head_pid_list == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001406 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
1407 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001408 return 1;
1409 }
1410
1411 return 0;
1412}
1413
Cyril Bontéac92a062014-12-27 22:28:38 +01001414/* helper macro to set an environment variable and jump to a specific label on failure. */
1415#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001416
1417/*
Cyril Bontéac92a062014-12-27 22:28:38 +01001418 * helper function to allocate enough memory to store an environment variable.
1419 * It will also check that the environment variable is updatable, and silently
1420 * fail if not.
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001421 */
Cyril Bontéac92a062014-12-27 22:28:38 +01001422static int extchk_setenv(struct check *check, int idx, const char *value)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001423{
1424 int len, ret;
Cyril Bontéac92a062014-12-27 22:28:38 +01001425 char *envname;
1426 int vmaxlen;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001427
Cyril Bontéac92a062014-12-27 22:28:38 +01001428 if (idx < 0 || idx >= EXTCHK_SIZE) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001429 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
Cyril Bontéac92a062014-12-27 22:28:38 +01001430 return 1;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001431 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001432
1433 envname = extcheck_envs[idx].name;
1434 vmaxlen = extcheck_envs[idx].vmaxlen;
1435
1436 /* Check if the environment variable is already set, and silently reject
1437 * the update if this one is not updatable. */
1438 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
1439 return 0;
1440
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001441 /* Instead of sending NOT_USED, sending an empty value is preferable */
1442 if (strcmp(value, "NOT_USED") == 0) {
1443 value = "";
1444 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001445
1446 len = strlen(envname) + 1;
1447 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
1448 len += strlen(value);
1449 else
1450 len += vmaxlen;
1451
1452 if (!check->envp[idx])
1453 check->envp[idx] = malloc(len + 1);
1454
1455 if (!check->envp[idx]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001456 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001457 return 1;
1458 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001459 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001460 if (ret < 0) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001461 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001462 return 1;
1463 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001464 else if (ret > len) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001465 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001466 return 1;
1467 }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001468 return 0;
1469}
Simon Horman98637e52014-06-20 12:30:16 +09001470
1471static int prepare_external_check(struct check *check)
1472{
1473 struct server *s = check->server;
1474 struct proxy *px = s->proxy;
1475 struct listener *listener = NULL, *l;
1476 int i;
Simon Horman98637e52014-06-20 12:30:16 +09001477 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001478 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001479
1480 list_for_each_entry(l, &px->conf.listeners, by_fe)
1481 /* Use the first INET, INET6 or UNIX listener */
1482 if (l->addr.ss_family == AF_INET ||
1483 l->addr.ss_family == AF_INET6 ||
1484 l->addr.ss_family == AF_UNIX) {
1485 listener = l;
1486 break;
1487 }
1488
Simon Horman98637e52014-06-20 12:30:16 +09001489 check->curpid = NULL;
Cyril Bontéac92a062014-12-27 22:28:38 +01001490 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
1491 if (!check->envp) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001492 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
Cyril Bontéac92a062014-12-27 22:28:38 +01001493 goto err;
1494 }
Simon Horman98637e52014-06-20 12:30:16 +09001495
Cyril Bontéac92a062014-12-27 22:28:38 +01001496 check->argv = calloc(6, sizeof(char *));
1497 if (!check->argv) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001498 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001499 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001500 }
Simon Horman98637e52014-06-20 12:30:16 +09001501
1502 check->argv[0] = px->check_command;
1503
Cyril Bonté777be862014-12-02 21:21:35 +01001504 if (!listener) {
1505 check->argv[1] = strdup("NOT_USED");
1506 check->argv[2] = strdup("NOT_USED");
1507 }
1508 else if (listener->addr.ss_family == AF_INET ||
Simon Horman98637e52014-06-20 12:30:16 +09001509 listener->addr.ss_family == AF_INET6) {
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001510 addr_to_str(&listener->addr, buf, sizeof(buf));
1511 check->argv[1] = strdup(buf);
1512 port_to_str(&listener->addr, buf, sizeof(buf));
1513 check->argv[2] = strdup(buf);
Cyril Bonté777be862014-12-02 21:21:35 +01001514 }
1515 else if (listener->addr.ss_family == AF_UNIX) {
Simon Horman98637e52014-06-20 12:30:16 +09001516 const struct sockaddr_un *un;
1517
1518 un = (struct sockaddr_un *)&listener->addr;
1519 check->argv[1] = strdup(un->sun_path);
1520 check->argv[2] = strdup("NOT_USED");
Cyril Bonté777be862014-12-02 21:21:35 +01001521 }
1522 else {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001523 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001524 goto err;
1525 }
1526
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001527 if (!check->argv[1] || !check->argv[2]) {
1528 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1529 goto err;
1530 }
1531
1532 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
1533 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
1534 if (!check->argv[3] || !check->argv[4]) {
1535 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1536 goto err;
1537 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001538
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001539 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
Willy Tarreau04276f32017-01-06 17:41:29 +01001540 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001541 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Simon Horman98637e52014-06-20 12:30:16 +09001542
Cyril Bontéac92a062014-12-27 22:28:38 +01001543 for (i = 0; i < 5; i++) {
1544 if (!check->argv[i]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001545 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001546 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001547 }
1548 }
Simon Horman98637e52014-06-20 12:30:16 +09001549
Cyril Bontéac92a062014-12-27 22:28:38 +01001550 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001551 /* Add proxy environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001552 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
1553 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
1554 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
1555 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001556 /* Add server environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001557 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
1558 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
1559 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
1560 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
1561 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
1562 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
1563
1564 /* Ensure that we don't leave any hole in check->envp */
1565 for (i = 0; i < EXTCHK_SIZE; i++)
1566 if (!check->envp[i])
1567 EXTCHK_SETENV(check, i, "", err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001568
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001569 return 1;
Simon Horman98637e52014-06-20 12:30:16 +09001570err:
1571 if (check->envp) {
Cyril Bontéac92a062014-12-27 22:28:38 +01001572 for (i = 0; i < EXTCHK_SIZE; i++)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001573 free(check->envp[i]);
Simon Horman98637e52014-06-20 12:30:16 +09001574 free(check->envp);
1575 check->envp = NULL;
1576 }
1577
1578 if (check->argv) {
1579 for (i = 1; i < 5; i++)
1580 free(check->argv[i]);
1581 free(check->argv);
1582 check->argv = NULL;
1583 }
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001584 return 0;
Simon Horman98637e52014-06-20 12:30:16 +09001585}
1586
Simon Hormanb00d17a2014-06-13 16:18:16 +09001587/*
Simon Horman98637e52014-06-20 12:30:16 +09001588 * establish a server health-check that makes use of a process.
1589 *
1590 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001591 * - SF_ERR_NONE if everything's OK
Willy Tarreaue7dff022015-04-03 01:14:29 +02001592 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
Tim Düsterhus4896c442016-11-29 02:15:19 +01001593 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Horman98637e52014-06-20 12:30:16 +09001594 *
1595 * Blocks and then unblocks SIGCHLD
1596 */
1597static int connect_proc_chk(struct task *t)
1598{
Cyril Bontéac92a062014-12-27 22:28:38 +01001599 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001600 struct check *check = t->context;
1601 struct server *s = check->server;
1602 struct proxy *px = s->proxy;
1603 int status;
1604 pid_t pid;
1605
Willy Tarreaue7dff022015-04-03 01:14:29 +02001606 status = SF_ERR_RESOURCE;
Simon Horman98637e52014-06-20 12:30:16 +09001607
1608 block_sigchld();
1609
1610 pid = fork();
1611 if (pid < 0) {
Willy Tarreaud96f1122019-12-03 07:07:36 +01001612 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
1613 (global.tune.options & GTUNE_INSECURE_FORK) ?
1614 "" : " (likely caused by missing 'insecure-fork-wanted')",
Christopher Faulet767a84b2017-11-24 16:50:31 +01001615 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001616 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1617 goto out;
1618 }
1619 if (pid == 0) {
1620 /* Child */
1621 extern char **environ;
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001622 struct rlimit limit;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001623 int fd;
1624
1625 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
1626 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
1627
Willy Tarreau2555ccf2019-02-21 22:22:06 +01001628 my_closefrom(fd);
Willy Tarreaub7b24782016-06-21 15:32:29 +02001629
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001630 /* restore the initial FD limits */
1631 limit.rlim_cur = rlim_fd_cur_at_boot;
1632 limit.rlim_max = rlim_fd_max_at_boot;
1633 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
1634 getrlimit(RLIMIT_NOFILE, &limit);
1635 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
1636 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
1637 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
1638 }
1639
Simon Horman98637e52014-06-20 12:30:16 +09001640 environ = check->envp;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001641
1642 /* Update some environment variables and command args: curconn, server addr and server port */
Cyril Bontéac92a062014-12-27 22:28:38 +01001643 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001644
1645 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
1646 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
1647
1648 *check->argv[4] = 0;
1649 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1650 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
1651 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
1652
Willy Tarreau2df8cad2019-07-01 07:51:29 +02001653 haproxy_unblock_signals();
Simon Horman98637e52014-06-20 12:30:16 +09001654 execvp(px->check_command, check->argv);
Christopher Faulet767a84b2017-11-24 16:50:31 +01001655 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
1656 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001657 exit(-1);
1658 }
1659
1660 /* Parent */
1661 if (check->result == CHK_RES_UNKNOWN) {
1662 if (pid_list_add(pid, t) != NULL) {
1663 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1664
1665 if (px->timeout.check && px->timeout.connect) {
1666 int t_con = tick_add(now_ms, px->timeout.connect);
1667 t->expire = tick_first(t->expire, t_con);
1668 }
Willy Tarreaue7dff022015-04-03 01:14:29 +02001669 status = SF_ERR_NONE;
Simon Horman98637e52014-06-20 12:30:16 +09001670 goto out;
1671 }
1672 else {
1673 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1674 }
1675 kill(pid, SIGTERM); /* process creation error */
1676 }
1677 else
1678 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1679
1680out:
1681 unblock_sigchld();
1682 return status;
1683}
1684
1685/*
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001686 * manages a server health-check that uses an external process. Returns
Willy Tarreaubaaee002006-06-26 02:48:02 +02001687 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001688 *
1689 * Please do NOT place any return statement in this function and only leave
1690 * via the out_unlock label.
Willy Tarreaubaaee002006-06-26 02:48:02 +02001691 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001692static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09001693{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001694 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09001695 struct server *s = check->server;
Simon Horman98637e52014-06-20 12:30:16 +09001696 int rv;
1697 int ret;
1698 int expired = tick_is_expired(t->expire, now_ms);
1699
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001700 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09001701 if (!(check->state & CHK_ST_INPROGRESS)) {
1702 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001703 if (!expired) /* woke up too early */
1704 goto out_unlock;
Simon Horman98637e52014-06-20 12:30:16 +09001705
1706 /* we don't send any health-checks when the proxy is
1707 * stopped, the server should not be checked or the check
1708 * is disabled.
1709 */
1710 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
1711 s->proxy->state == PR_STSTOPPED)
1712 goto reschedule;
1713
1714 /* we'll initiate a new check */
1715 set_server_check_status(check, HCHK_STATUS_START, NULL);
1716
1717 check->state |= CHK_ST_INPROGRESS;
1718
Simon Hormandbf70192015-01-30 11:22:53 +09001719 ret = connect_proc_chk(t);
Willy Tarreaud7c3fbd2017-10-04 15:19:26 +02001720 if (ret == SF_ERR_NONE) {
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001721 /* the process was forked, we allow up to min(inter,
1722 * timeout.connect) for it to report its status, but
1723 * only when timeout.check is set as it may be to short
1724 * for a full check otherwise.
Simon Horman98637e52014-06-20 12:30:16 +09001725 */
1726 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1727
1728 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
1729 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
1730 t->expire = tick_first(t->expire, t_con);
1731 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02001732 task_set_affinity(t, tid_bit);
Simon Horman98637e52014-06-20 12:30:16 +09001733 goto reschedule;
Simon Horman98637e52014-06-20 12:30:16 +09001734 }
1735
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001736 /* here, we failed to start the check */
Simon Horman98637e52014-06-20 12:30:16 +09001737
1738 check->state &= ~CHK_ST_INPROGRESS;
1739 check_notify_failure(check);
1740
1741 /* we allow up to min(inter, timeout.connect) for a connection
1742 * to establish but only when timeout.check is set
1743 * as it may be to short for a full check otherwise
1744 */
1745 while (tick_is_expired(t->expire, now_ms)) {
1746 int t_con;
1747
1748 t_con = tick_add(t->expire, s->proxy->timeout.connect);
1749 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
1750
1751 if (s->proxy->timeout.check)
1752 t->expire = tick_first(t->expire, t_con);
1753 }
1754 }
1755 else {
1756 /* there was a test running.
1757 * First, let's check whether there was an uncaught error,
1758 * which can happen on connect timeout or error.
1759 */
1760 if (check->result == CHK_RES_UNKNOWN) {
1761 /* good connection is enough for pure TCP check */
1762 struct pid_list *elem = check->curpid;
1763 int status = HCHK_STATUS_UNKNOWN;
1764
1765 if (elem->exited) {
1766 status = elem->status; /* Save in case the process exits between use below */
1767 if (!WIFEXITED(status))
1768 check->code = -1;
1769 else
1770 check->code = WEXITSTATUS(status);
1771 if (!WIFEXITED(status) || WEXITSTATUS(status))
1772 status = HCHK_STATUS_PROCERR;
1773 else
1774 status = HCHK_STATUS_PROCOK;
1775 } else if (expired) {
1776 status = HCHK_STATUS_PROCTOUT;
Christopher Faulet767a84b2017-11-24 16:50:31 +01001777 ha_warning("kill %d\n", (int)elem->pid);
Simon Horman98637e52014-06-20 12:30:16 +09001778 kill(elem->pid, SIGTERM);
1779 }
1780 set_server_check_status(check, status, NULL);
1781 }
1782
1783 if (check->result == CHK_RES_FAILED) {
1784 /* a failure or timeout detected */
1785 check_notify_failure(check);
1786 }
1787 else if (check->result == CHK_RES_CONDPASS) {
1788 /* check is OK but asks for stopping mode */
1789 check_notify_stopping(check);
1790 }
1791 else if (check->result == CHK_RES_PASSED) {
1792 /* a success was detected */
1793 check_notify_success(check);
1794 }
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001795 task_set_affinity(t, 1);
Simon Horman98637e52014-06-20 12:30:16 +09001796 check->state &= ~CHK_ST_INPROGRESS;
1797
1798 pid_list_del(check->curpid);
1799
1800 rv = 0;
1801 if (global.spread_checks > 0) {
1802 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01001803 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Simon Horman98637e52014-06-20 12:30:16 +09001804 }
1805 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
1806 }
1807
1808 reschedule:
1809 while (tick_is_expired(t->expire, now_ms))
1810 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001811
1812 out_unlock:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001813 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09001814 return t;
1815}
1816
1817/*
1818 * manages a server health-check that uses a connection. Returns
1819 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001820 *
1821 * Please do NOT place any return statement in this function and only leave
1822 * via the out_unlock label.
Simon Horman98637e52014-06-20 12:30:16 +09001823 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001824static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001825{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001826 struct check *check = context;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001827 struct proxy *proxy = check->proxy;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001828 struct conn_stream *cs = check->cs;
1829 struct connection *conn = cs_conn(cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001830 int rv;
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001831 int ret;
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01001832 int expired = tick_is_expired(t->expire, now_ms);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001833
Olivier Houchard0923fa42019-01-11 18:43:04 +01001834 if (check->server)
1835 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau2c115e52013-12-11 19:41:16 +01001836 if (!(check->state & CHK_ST_INPROGRESS)) {
Willy Tarreau5a78f362012-11-23 12:47:05 +01001837 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001838 if (!expired) /* woke up too early */
1839 goto out_unlock;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001840
Simon Horman671b6f02013-11-25 10:46:39 +09001841 /* we don't send any health-checks when the proxy is
1842 * stopped, the server should not be checked or the check
1843 * is disabled.
Willy Tarreaubaaee002006-06-26 02:48:02 +02001844 */
Willy Tarreau0d924cc2013-12-11 21:26:24 +01001845 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001846 proxy->state == PR_STSTOPPED)
Willy Tarreau5a78f362012-11-23 12:47:05 +01001847 goto reschedule;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001848
1849 /* we'll initiate a new check */
Simon Horman4a741432013-02-23 15:35:38 +09001850 set_server_check_status(check, HCHK_STATUS_START, NULL);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001851
Willy Tarreau2c115e52013-12-11 19:41:16 +01001852 check->state |= CHK_ST_INPROGRESS;
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001853 b_reset(&check->bi);
1854 b_reset(&check->bo);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001855
Olivier Houchardaebeff72019-11-29 16:18:51 +01001856 task_set_affinity(t, tid_bit);
Simon Hormandbf70192015-01-30 11:22:53 +09001857 ret = connect_conn_chk(t);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001858 cs = check->cs;
1859 conn = cs_conn(cs);
Willy Tarreau00149122017-10-04 18:05:01 +02001860
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001861 switch (ret) {
Willy Tarreaue7dff022015-04-03 01:14:29 +02001862 case SF_ERR_UP:
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001863 goto out_unlock;
1864
Willy Tarreaue7dff022015-04-03 01:14:29 +02001865 case SF_ERR_NONE:
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001866 /* we allow up to min(inter, timeout.connect) for a connection
1867 * to establish but only when timeout.check is set
1868 * as it may be to short for a full check otherwise
1869 */
Simon Horman4a741432013-02-23 15:35:38 +09001870 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001871 if (proxy->timeout.check && proxy->timeout.connect) {
1872 int t_con = tick_add(now_ms, proxy->timeout.connect);
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001873 t->expire = tick_first(t->expire, t_con);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001874 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01001875
Willy Tarreaucc705a62019-09-05 17:51:30 +02001876 if (check->type) {
1877 /* send the request if we have one. We avoid receiving
1878 * if not connected, unless we didn't subscribe for
1879 * sending since otherwise we won't be woken up.
1880 */
1881 __event_srv_chk_w(cs);
Willy Tarreau911db9b2020-01-23 16:27:54 +01001882 if (!(conn->flags & CO_FL_WAIT_XPRT) ||
Willy Tarreauc5940392019-09-05 17:38:40 +02001883 !(check->wait_list.events & SUB_RETRY_SEND))
1884 __event_srv_chk_r(cs);
Willy Tarreaucc705a62019-09-05 17:51:30 +02001885 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01001886
Willy Tarreau5a78f362012-11-23 12:47:05 +01001887 goto reschedule;
1888
Willy Tarreaue7dff022015-04-03 01:14:29 +02001889 case SF_ERR_SRVTO: /* ETIMEDOUT */
1890 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
Willy Tarreau00149122017-10-04 18:05:01 +02001891 if (conn)
1892 conn->flags |= CO_FL_ERROR;
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001893 chk_report_conn_err(check, errno, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01001894 break;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001895 /* should share same code than cases below */
1896 case SF_ERR_CHK_PORT:
1897 check->state |= CHK_ST_PORT_MISS;
Willy Tarreaue7dff022015-04-03 01:14:29 +02001898 case SF_ERR_PRXCOND:
1899 case SF_ERR_RESOURCE:
1900 case SF_ERR_INTERNAL:
Willy Tarreau00149122017-10-04 18:05:01 +02001901 if (conn)
1902 conn->flags |= CO_FL_ERROR;
1903 chk_report_conn_err(check, conn ? 0 : ENOMEM, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01001904 break;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001905 }
1906
Willy Tarreau5a78f362012-11-23 12:47:05 +01001907 /* here, we have seen a synchronous error, no fd was allocated */
Olivier Houchardaebeff72019-11-29 16:18:51 +01001908 task_set_affinity(t, MAX_THREADS_MASK);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001909 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02001910 if (check->wait_list.events)
1911 cs->conn->xprt->unsubscribe(cs->conn,
1912 cs->conn->xprt_ctx,
1913 check->wait_list.events,
1914 &check->wait_list);
1915 /* We may have been scheduled to run, and the
1916 * I/O handler expects to have a cs, so remove
1917 * the tasklet
1918 */
Willy Tarreau86eded62019-06-14 14:47:49 +02001919 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001920 cs_destroy(cs);
1921 cs = check->cs = NULL;
1922 conn = NULL;
Olivier Houchard390485a2017-10-24 19:03:30 +02001923 }
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001924
Willy Tarreau2c115e52013-12-11 19:41:16 +01001925 check->state &= ~CHK_ST_INPROGRESS;
Willy Tarreau4eec5472014-05-20 22:32:27 +02001926 check_notify_failure(check);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001927
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001928 /* we allow up to min(inter, timeout.connect) for a connection
1929 * to establish but only when timeout.check is set
1930 * as it may be to short for a full check otherwise
1931 */
Willy Tarreau0c303ee2008-07-07 00:09:58 +02001932 while (tick_is_expired(t->expire, now_ms)) {
1933 int t_con;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001934
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001935 t_con = tick_add(t->expire, proxy->timeout.connect);
Simon Horman4a741432013-02-23 15:35:38 +09001936 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001937 if (proxy->timeout.check)
Willy Tarreau0c303ee2008-07-07 00:09:58 +02001938 t->expire = tick_first(t->expire, t_con);
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001939 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02001940 }
1941 else {
Willy Tarreauf1503172012-09-28 19:39:36 +02001942 /* there was a test running.
1943 * First, let's check whether there was an uncaught error,
1944 * which can happen on connect timeout or error.
1945 */
Simon Hormanccaabcd2014-06-20 12:29:47 +09001946 if (check->result == CHK_RES_UNKNOWN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001947 /* good connection is enough for pure TCP check */
Willy Tarreau911db9b2020-01-23 16:27:54 +01001948 if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Christopher Fauletf61f33a2020-03-27 18:55:49 +01001949 if (check->use_ssl == 1)
Simon Horman4a741432013-02-23 15:35:38 +09001950 set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
Willy Tarreauf1503172012-09-28 19:39:36 +02001951 else
Simon Horman4a741432013-02-23 15:35:38 +09001952 set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01001953 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +02001954 else if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001955 chk_report_conn_err(check, 0, expired);
Willy Tarreauf1503172012-09-28 19:39:36 +02001956 }
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01001957 else
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001958 goto out_unlock; /* timeout not reached, wait again */
Willy Tarreauf1503172012-09-28 19:39:36 +02001959 }
1960
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01001961 /* check complete or aborted */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001962
1963 check->current_step = NULL;
1964 if (check->sess != NULL) {
1965 session_free(check->sess);
1966 check->sess = NULL;
1967 }
1968
Willy Tarreau00149122017-10-04 18:05:01 +02001969 if (conn && conn->xprt) {
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001970 /* The check was aborted and the connection was not yet closed.
1971 * This can happen upon timeout, or when an external event such
1972 * as a failed response coupled with "observe layer7" caused the
1973 * server state to be suddenly changed.
1974 */
Willy Tarreaud85c4852015-03-13 00:40:28 +01001975 conn_sock_drain(conn);
Willy Tarreaua553ae92017-10-05 18:52:17 +02001976 cs_close(cs);
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001977 }
1978
Willy Tarreauac59f362017-10-08 11:10:19 +02001979 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02001980 if (check->wait_list.events)
1981 cs->conn->xprt->unsubscribe(cs->conn,
1982 cs->conn->xprt_ctx,
1983 check->wait_list.events,
1984 &check->wait_list);
1985 /* We may have been scheduled to run, and the
Willy Tarreau86eded62019-06-14 14:47:49 +02001986 * I/O handler expects to have a cs, so remove
1987 * the tasklet
1988 */
1989 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001990 cs_destroy(cs);
1991 cs = check->cs = NULL;
1992 conn = NULL;
Willy Tarreau00149122017-10-04 18:05:01 +02001993 }
1994
Olivier Houchard0923fa42019-01-11 18:43:04 +01001995 if (check->server) {
1996 if (check->result == CHK_RES_FAILED) {
1997 /* a failure or timeout detected */
1998 check_notify_failure(check);
1999 }
2000 else if (check->result == CHK_RES_CONDPASS) {
2001 /* check is OK but asks for stopping mode */
2002 check_notify_stopping(check);
2003 }
2004 else if (check->result == CHK_RES_PASSED) {
2005 /* a success was detected */
2006 check_notify_success(check);
2007 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02002008 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02002009 task_set_affinity(t, MAX_THREADS_MASK);
Willy Tarreau2c115e52013-12-11 19:41:16 +01002010 check->state &= ~CHK_ST_INPROGRESS;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002011
Olivier Houchard0923fa42019-01-11 18:43:04 +01002012 if (check->server) {
2013 rv = 0;
2014 if (global.spread_checks > 0) {
2015 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01002016 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Olivier Houchard0923fa42019-01-11 18:43:04 +01002017 }
2018 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Willy Tarreaubaaee002006-06-26 02:48:02 +02002019 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02002020 }
Willy Tarreau5a78f362012-11-23 12:47:05 +01002021
2022 reschedule:
2023 while (tick_is_expired(t->expire, now_ms))
Simon Horman4a741432013-02-23 15:35:38 +09002024 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01002025 out_unlock:
Olivier Houchard0923fa42019-01-11 18:43:04 +01002026 if (check->server)
2027 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau26c25062009-03-08 09:38:41 +01002028 return t;
Willy Tarreaubaaee002006-06-26 02:48:02 +02002029}
2030
Simon Horman98637e52014-06-20 12:30:16 +09002031/*
2032 * manages a server health-check. Returns
2033 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
2034 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02002035static struct task *process_chk(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09002036{
Olivier Houchard9f6af332018-05-25 14:04:04 +02002037 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09002038
2039 if (check->type == PR_O2_EXT_CHK)
Olivier Houchard9f6af332018-05-25 14:04:04 +02002040 return process_chk_proc(t, context, state);
2041 return process_chk_conn(t, context, state);
Baptiste Assmanna68ca962015-04-14 01:15:08 +02002042
Simon Horman98637e52014-06-20 12:30:16 +09002043}
2044
Simon Horman5c942422013-11-25 10:46:32 +09002045static int start_check_task(struct check *check, int mininter,
2046 int nbcheck, int srvpos)
2047{
2048 struct task *t;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002049 unsigned long thread_mask = MAX_THREADS_MASK;
2050
2051 if (check->type == PR_O2_EXT_CHK)
2052 thread_mask = 1;
2053
Simon Horman5c942422013-11-25 10:46:32 +09002054 /* task for the check */
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002055 if ((t = task_new(thread_mask)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002056 ha_alert("Starting [%s:%s] check: out of memory.\n",
2057 check->server->proxy->id, check->server->id);
Simon Horman5c942422013-11-25 10:46:32 +09002058 return 0;
2059 }
2060
2061 check->task = t;
2062 t->process = process_chk;
2063 t->context = check;
2064
Willy Tarreau1746eec2014-04-25 10:46:47 +02002065 if (mininter < srv_getinter(check))
2066 mininter = srv_getinter(check);
2067
2068 if (global.max_spread_checks && mininter > global.max_spread_checks)
2069 mininter = global.max_spread_checks;
2070
Simon Horman5c942422013-11-25 10:46:32 +09002071 /* check this every ms */
Willy Tarreau1746eec2014-04-25 10:46:47 +02002072 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
Simon Horman5c942422013-11-25 10:46:32 +09002073 check->start = now;
2074 task_queue(t);
2075
2076 return 1;
2077}
2078
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002079/*
2080 * Start health-check.
Willy Tarreau865c5142016-12-21 20:04:48 +01002081 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002082 */
Willy Tarreau865c5142016-12-21 20:04:48 +01002083static int start_checks()
2084{
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002085
2086 struct proxy *px;
2087 struct server *s;
2088 struct task *t;
Simon Horman4a741432013-02-23 15:35:38 +09002089 int nbcheck=0, mininter=0, srvpos=0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002090
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002091 /* 0- init the dummy frontend used to create all checks sessions */
2092 init_new_proxy(&checks_fe);
2093 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
2094 checks_fe.mode = PR_MODE_TCP;
2095 checks_fe.maxconn = 0;
2096 checks_fe.conn_retries = CONN_RETRIES;
2097 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
2098 checks_fe.timeout.client = TICK_ETERNITY;
2099
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002100 /* 1- count the checkers to run simultaneously.
2101 * We also determine the minimum interval among all of those which
2102 * have an interval larger than SRV_CHK_INTER_THRES. This interval
2103 * will be used to spread their start-up date. Those which have
Jamie Gloudon801a0a32012-08-25 00:18:33 -04002104 * a shorter interval will start independently and will not dictate
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002105 * too short an interval for all others.
2106 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002107 for (px = proxies_list; px; px = px->next) {
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002108 for (s = px->srv; s; s = s->next) {
Willy Tarreaue7b73482013-11-21 11:50:50 +01002109 if (s->slowstart) {
Emeric Brunc60def82017-09-27 14:59:38 +02002110 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002111 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002112 return ERR_ALERT | ERR_FATAL;
Willy Tarreaue7b73482013-11-21 11:50:50 +01002113 }
2114 /* We need a warmup task that will be called when the server
2115 * state switches from down to up.
2116 */
2117 s->warmup = t;
2118 t->process = server_warmup;
2119 t->context = s;
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002120 /* server can be in this state only because of */
Emeric Brun52a91d32017-08-31 14:41:55 +02002121 if (s->next_state == SRV_ST_STARTING)
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002122 task_schedule(s->warmup, tick_add(now_ms, MS_TO_TICKS(MAX(1000, (now.tv_sec - s->last_change)) / 20)));
Willy Tarreaue7b73482013-11-21 11:50:50 +01002123 }
2124
Willy Tarreaud8514a22013-12-11 21:10:14 +01002125 if (s->check.state & CHK_ST_CONFIGURED) {
2126 nbcheck++;
2127 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
2128 (!mininter || mininter > srv_getinter(&s->check)))
2129 mininter = srv_getinter(&s->check);
2130 }
Willy Tarreau15f39102013-12-11 20:41:18 +01002131
Willy Tarreaud8514a22013-12-11 21:10:14 +01002132 if (s->agent.state & CHK_ST_CONFIGURED) {
2133 nbcheck++;
2134 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
2135 (!mininter || mininter > srv_getinter(&s->agent)))
2136 mininter = srv_getinter(&s->agent);
2137 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002138 }
2139 }
2140
Simon Horman4a741432013-02-23 15:35:38 +09002141 if (!nbcheck)
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002142 return 0;
2143
2144 srand((unsigned)time(NULL));
2145
2146 /*
2147 * 2- start them as far as possible from each others. For this, we will
2148 * start them after their interval set to the min interval divided by
2149 * the number of servers, weighted by the server's position in the list.
2150 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002151 for (px = proxies_list; px; px = px->next) {
Simon Horman98637e52014-06-20 12:30:16 +09002152 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
2153 if (init_pid_list()) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002154 ha_alert("Starting [%s] check: out of memory.\n", px->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002155 return ERR_ALERT | ERR_FATAL;
Simon Horman98637e52014-06-20 12:30:16 +09002156 }
2157 }
2158
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002159 for (s = px->srv; s; s = s->next) {
Simon Hormand60d6912013-11-25 10:46:36 +09002160 /* A task for the main check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002161 if (s->check.state & CHK_ST_CONFIGURED) {
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002162 if (s->check.type == PR_O2_EXT_CHK) {
2163 if (!prepare_external_check(&s->check))
Willy Tarreau865c5142016-12-21 20:04:48 +01002164 return ERR_ALERT | ERR_FATAL;
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002165 }
Simon Hormand60d6912013-11-25 10:46:36 +09002166 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
Willy Tarreau865c5142016-12-21 20:04:48 +01002167 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002168 srvpos++;
2169 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002170
Simon Hormand60d6912013-11-25 10:46:36 +09002171 /* A task for a auxiliary agent check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002172 if (s->agent.state & CHK_ST_CONFIGURED) {
Simon Hormand60d6912013-11-25 10:46:36 +09002173 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
Willy Tarreau865c5142016-12-21 20:04:48 +01002174 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002175 }
2176 srvpos++;
2177 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002178 }
2179 }
2180 return 0;
2181}
Willy Tarreaubaaee002006-06-26 02:48:02 +02002182
2183/*
Willy Tarreau5b3a2022012-09-28 15:01:02 +02002184 * Perform content verification check on data in s->check.buffer buffer.
Willy Tarreaubd741542010-03-16 18:46:54 +01002185 * The buffer MUST be terminated by a null byte before calling this function.
2186 * Sets server status appropriately. The caller is responsible for ensuring
2187 * that the buffer contains at least 13 characters. If <done> is zero, we may
2188 * return 0 to indicate that data is required to decide of a match.
2189 */
2190static int httpchk_expect(struct server *s, int done)
2191{
Christopher Faulet1bc04c72017-10-29 20:14:08 +01002192 static THREAD_LOCAL char status_msg[] = "HTTP status check returned code <000>";
Willy Tarreaubd741542010-03-16 18:46:54 +01002193 char status_code[] = "000";
2194 char *contentptr;
2195 int crlf;
2196 int ret;
2197
2198 switch (s->proxy->options2 & PR_O2_EXP_TYPE) {
2199 case PR_O2_EXP_STS:
2200 case PR_O2_EXP_RSTS:
Willy Tarreauc9fa0482018-07-10 17:43:27 +02002201 memcpy(status_code, b_head(&s->check.bi) + 9, 3);
2202 memcpy(status_msg + strlen(status_msg) - 4, b_head(&s->check.bi) + 9, 3);
Willy Tarreaubd741542010-03-16 18:46:54 +01002203
2204 if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STS)
2205 ret = strncmp(s->proxy->expect_str, status_code, 3) == 0;
2206 else
Thierry FOURNIER09af0d62014-06-18 11:35:54 +02002207 ret = regex_exec(s->proxy->expect_regex, status_code);
Willy Tarreaubd741542010-03-16 18:46:54 +01002208
2209 /* we necessarily have the response, so there are no partial failures */
2210 if (s->proxy->options2 & PR_O2_EXP_INV)
2211 ret = !ret;
2212
Simon Horman4a741432013-02-23 15:35:38 +09002213 set_server_check_status(&s->check, ret ? HCHK_STATUS_L7OKD : HCHK_STATUS_L7STS, status_msg);
Willy Tarreaubd741542010-03-16 18:46:54 +01002214 break;
2215
2216 case PR_O2_EXP_STR:
2217 case PR_O2_EXP_RSTR:
2218 /* very simple response parser: ignore CR and only count consecutive LFs,
2219 * stop with contentptr pointing to first char after the double CRLF or
2220 * to '\0' if crlf < 2.
2221 */
2222 crlf = 0;
Willy Tarreauc9fa0482018-07-10 17:43:27 +02002223 for (contentptr = b_head(&s->check.bi); *contentptr; contentptr++) {
Willy Tarreaubd741542010-03-16 18:46:54 +01002224 if (crlf >= 2)
2225 break;
2226 if (*contentptr == '\r')
2227 continue;
2228 else if (*contentptr == '\n')
2229 crlf++;
2230 else
2231 crlf = 0;
2232 }
2233
2234 /* Check that response contains a body... */
2235 if (crlf < 2) {
2236 if (!done)
2237 return 0;
2238
Simon Horman4a741432013-02-23 15:35:38 +09002239 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002240 "HTTP content check could not find a response body");
2241 return 1;
2242 }
2243
2244 /* Check that response body is not empty... */
2245 if (*contentptr == '\0') {
Willy Tarreaua164fb52011-04-13 09:32:41 +02002246 if (!done)
2247 return 0;
2248
Simon Horman4a741432013-02-23 15:35:38 +09002249 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002250 "HTTP content check found empty response body");
2251 return 1;
2252 }
2253
2254 /* Check the response content against the supplied string
2255 * or regex... */
2256 if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STR)
2257 ret = strstr(contentptr, s->proxy->expect_str) != NULL;
2258 else
Thierry FOURNIER09af0d62014-06-18 11:35:54 +02002259 ret = regex_exec(s->proxy->expect_regex, contentptr);
Willy Tarreaubd741542010-03-16 18:46:54 +01002260
2261 /* if we don't match, we may need to wait more */
2262 if (!ret && !done)
2263 return 0;
2264
2265 if (ret) {
2266 /* content matched */
2267 if (s->proxy->options2 & PR_O2_EXP_INV)
Simon Horman4a741432013-02-23 15:35:38 +09002268 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002269 "HTTP check matched unwanted content");
2270 else
Simon Horman4a741432013-02-23 15:35:38 +09002271 set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
Willy Tarreaubd741542010-03-16 18:46:54 +01002272 "HTTP content check matched");
2273 }
2274 else {
2275 if (s->proxy->options2 & PR_O2_EXP_INV)
Simon Horman4a741432013-02-23 15:35:38 +09002276 set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
Willy Tarreaubd741542010-03-16 18:46:54 +01002277 "HTTP check did not match unwanted content");
2278 else
Simon Horman4a741432013-02-23 15:35:38 +09002279 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002280 "HTTP content check did not match");
2281 }
2282 break;
2283 }
2284 return 1;
2285}
2286
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002287/*
2288 * return the id of a step in a send/expect session
2289 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002290static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002291{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002292 if (!rule)
2293 rule = check->current_step;
Willy Tarreau213c6782014-10-02 14:51:02 +02002294
Christopher Faulet3c29aa62020-03-24 13:31:19 +01002295 /* no last started step => first step */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002296 if (!rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002297 return 1;
2298
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002299 /* last step is the first implicit connect */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002300 if (rule->index == 0 &&
2301 rule->action == TCPCHK_ACT_CONNECT &&
Christopher Fauletbb591a12020-04-01 16:52:17 +02002302 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002303 return 0;
2304
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002305 return rule->index + 1;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002306}
2307
Christopher Faulet206368d2020-04-03 14:51:06 +02002308static void tcpcheck_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2309 int match, struct ist info)
2310{
2311 struct sample *smp;
2312
2313 if (istlen(info)) {
2314 chunk_strncat(msg, info.ptr, info.len);
2315 goto comment;
2316 }
2317 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
2318 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
2319 goto comment;
2320 }
2321
Christopher Faulet799f3a42020-04-07 12:06:14 +02002322 if (check->type == PR_O2_TCPCHK_CHK && (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK))
2323 goto comment;
2324
Christopher Faulet206368d2020-04-03 14:51:06 +02002325 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
2326 switch (rule->expect.type) {
2327 case TCPCHK_EXPECT_STRING:
2328 chunk_appendf(msg, " '%s' at step %d", rule->expect.string, tcpcheck_get_step_id(check, rule));
2329 break;
2330 case TCPCHK_EXPECT_BINARY:
2331 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
2332 break;
2333 case TCPCHK_EXPECT_REGEX:
2334 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
2335 break;
2336 case TCPCHK_EXPECT_REGEX_BINARY:
2337 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
2338
2339 /* If references to the matched text were made, divide the
2340 * offsets by 2 to match offset of the original response buffer.
2341 */
2342 if (rule->expect.with_capture) {
2343 int i;
2344
2345 for (i = 1; i < MAX_MATCH && pmatch[i].rm_so != -1; i++) {
2346 pmatch[i].rm_so /= 2; /* at first matched char. */
2347 pmatch[i].rm_eo /= 2; /* at last matched char. */
2348 }
2349 }
2350 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02002351 case TCPCHK_EXPECT_CUSTOM:
2352 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
2353 break;
Christopher Faulet206368d2020-04-03 14:51:06 +02002354 case TCPCHK_EXPECT_UNDEF:
2355 /* Should never happen. */
2356 return;
2357 }
2358
2359 comment:
2360 if (rule->comment) {
2361 chunk_strcat(msg, " comment: ");
2362 if (rule->expect.with_capture) {
2363 int ret = exp_replace(b_tail(msg), b_room(msg), b_head(&check->bi), rule->comment, pmatch);
2364 if (ret != -1) /* ignore comment if too large */
2365 msg->data += ret;
2366 }
2367 else
2368 chunk_strcat(msg, rule->comment);
2369 }
2370
2371 if (rule->expect.status_expr) {
2372 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2373 rule->expect.status_expr, SMP_T_SINT);
2374 if (smp)
2375 check->code = smp->data.u.sint;
2376 }
2377
2378 *(b_tail(msg)) = '\0';
2379}
2380
2381static void tcpcheck_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2382 struct ist info)
2383{
2384 struct sample *smp;
2385
2386 if (istlen(info))
2387 chunk_strncat(msg, info.ptr, info.len);
2388 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
2389 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
2390 &rule->expect.onsuccess_fmt);
Christopher Faulet799f3a42020-04-07 12:06:14 +02002391 else if (check->type == PR_O2_TCPCHK_CHK && !(check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK))
Christopher Faulet206368d2020-04-03 14:51:06 +02002392 chunk_strcat(msg, "(tcp-check)");
2393
2394 if (rule->expect.status_expr) {
2395 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2396 rule->expect.status_expr, SMP_T_SINT);
2397 if (smp)
2398 check->code = smp->data.u.sint;
2399 }
2400
2401 *(b_tail(msg)) = '\0';
2402}
2403
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002404static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
2405 unsigned int offset, int last_read)
2406{
2407 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2408 enum healthcheck_status status;
2409 struct buffer *msg = NULL;
2410 struct ist desc = ist(NULL);
2411 unsigned int err = 0, plen = 0;
2412
2413
2414 /* 3 Bytes for the packet length and 1 byte for the sequence id */
2415 if (!last_read && b_data(&check->bi) < offset+4) {
2416 if (!last_read)
2417 goto wait_more_data;
2418
2419 /* invalid length or truncated response */
2420 status = HCHK_STATUS_L7RSP;
2421 goto error;
2422 }
2423
2424 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
2425 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
2426 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
2427
2428 if (b_data(&check->bi) < offset+plen+4) {
2429 if (!last_read)
2430 goto wait_more_data;
2431
2432 /* invalid length or truncated response */
2433 status = HCHK_STATUS_L7RSP;
2434 goto error;
2435 }
2436
2437 if (*b_peek(&check->bi, offset+4) == '\xff') {
2438 /* MySQL Error packet always begin with field_count = 0xff */
2439 status = HCHK_STATUS_L7STS;
2440 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
2441 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
2442 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
2443 goto error;
2444 }
2445
2446 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
2447 /* Not the last rule, continue */
2448 goto out;
2449 }
2450
2451 /* We set the MySQL Version in description for information purpose
2452 * FIXME : it can be cool to use MySQL Version for other purpose,
2453 * like mark as down old MySQL server.
2454 */
Christopher Fauletec07e382020-04-07 14:56:26 +02002455 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002456
2457 out:
2458 free_trash_chunk(msg);
2459 return ret;
2460
2461 error:
2462 ret = TCPCHK_EVAL_STOP;
2463 check->code = err;
2464 msg = alloc_trash_chunk();
2465 if (msg)
2466 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2467 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2468 goto out;
2469
2470 wait_more_data:
2471 ret = TCPCHK_EVAL_WAIT;
2472 goto out;
2473}
2474
2475
2476static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
2477{
2478 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
2479}
2480
2481static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
2482{
2483 unsigned int hslen = 0;
2484
2485 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
2486 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
2487 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
2488
2489 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
2490}
2491
Christopher Faulet1997eca2020-04-03 23:13:50 +02002492static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
2493{
2494 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2495 enum healthcheck_status status;
2496 struct buffer *msg = NULL;
2497 struct ist desc = ist(NULL);
2498 unsigned short msglen = 0;
2499
2500 /* Check if the server speaks LDAP (ASN.1/BER)
2501 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
2502 * http://tools.ietf.org/html/rfc4511
2503 */
2504 /* size of LDAPMessage */
2505 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
2506
2507 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
2508 * messageID: 0x02 0x01 0x01: INTEGER 1
2509 * protocolOp: 0x61: bindResponse
2510 */
2511 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
2512 status = HCHK_STATUS_L7RSP;
2513 desc = ist("Not LDAPv3 protocol");
2514 goto error;
2515 }
2516
2517 /* size of bindResponse */
2518 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
2519
2520 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2521 * ldapResult: 0x0a 0x01: ENUMERATION
2522 */
2523 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
2524 status = HCHK_STATUS_L7RSP;
2525 desc = ist("Not LDAPv3 protocol");
2526 goto error;
2527 }
2528
2529 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2530 * resultCode
2531 */
2532 check->code = *(b_head(&check->bi) + msglen + 9);
2533 if (check->code) {
2534 status = HCHK_STATUS_L7STS;
2535 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
2536 goto error;
2537 }
2538
Christopher Fauletec07e382020-04-07 14:56:26 +02002539 set_server_check_status(check, rule->expect.ok_status, "Success");
Christopher Faulet1997eca2020-04-03 23:13:50 +02002540
2541 out:
2542 free_trash_chunk(msg);
2543 return ret;
2544
2545 error:
2546 ret = TCPCHK_EVAL_STOP;
2547 msg = alloc_trash_chunk();
2548 if (msg)
2549 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2550 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2551 goto out;
2552
2553 wait_more_data:
2554 ret = TCPCHK_EVAL_WAIT;
2555 goto out;
2556}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002557
Christopher Faulet267b01b2020-04-04 10:27:09 +02002558
2559static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
2560{
2561 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2562 enum healthcheck_status status;
2563 struct buffer *msg = NULL;
2564 struct ist desc = ist(NULL);
2565 unsigned int framesz;
2566
2567
2568 memcpy(&framesz, b_head(&check->bi), 4);
2569 framesz = ntohl(framesz);
2570
2571 if (!last_read && b_data(&check->bi) < (4+framesz))
2572 goto wait_more_data;
2573
2574 memset(b_orig(&trash), 0, b_size(&trash));
2575 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
2576 status = HCHK_STATUS_L7RSP;
2577 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
2578 goto error;
2579 }
2580
Christopher Fauletec07e382020-04-07 14:56:26 +02002581 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Christopher Faulet267b01b2020-04-04 10:27:09 +02002582
2583 out:
2584 free_trash_chunk(msg);
2585 return ret;
2586
2587 error:
2588 ret = TCPCHK_EVAL_STOP;
2589 msg = alloc_trash_chunk();
2590 if (msg)
2591 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2592 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2593 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002594
2595 wait_more_data:
2596 ret = TCPCHK_EVAL_WAIT;
2597 goto out;
2598}
2599
2600static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
2601{
2602 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
2603 enum healthcheck_status status = HCHK_STATUS_CHECKED;
2604 const char *hs = NULL; /* health status */
2605 const char *as = NULL; /* admin status */
2606 const char *ps = NULL; /* performance status */
2607 const char *cs = NULL; /* maxconn */
2608 const char *err = NULL; /* first error to report */
2609 const char *wrn = NULL; /* first warning to report */
2610 char *cmd, *p;
2611
2612 /* We're getting an agent check response. The agent could
2613 * have been disabled in the mean time with a long check
2614 * still pending. It is important that we ignore the whole
2615 * response.
2616 */
2617 if (!(check->state & CHK_ST_ENABLED))
2618 goto out;
2619
2620 /* The agent supports strings made of a single line ended by the
2621 * first CR ('\r') or LF ('\n'). This line is composed of words
2622 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
2623 * line may optionally contained a description of a state change
2624 * after a sharp ('#'), which is only considered if a health state
2625 * is announced.
2626 *
2627 * Words may be composed of :
2628 * - a numeric weight suffixed by the percent character ('%').
2629 * - a health status among "up", "down", "stopped", and "fail".
2630 * - an admin status among "ready", "drain", "maint".
2631 *
2632 * These words may appear in any order. If multiple words of the
2633 * same category appear, the last one wins.
2634 */
2635
2636 p = b_head(&check->bi);
2637 while (*p && *p != '\n' && *p != '\r')
2638 p++;
2639
2640 if (!*p) {
2641 if (!last_read)
2642 goto wait_more_data;
2643
2644 /* at least inform the admin that the agent is mis-behaving */
2645 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
2646 goto out;
2647 }
2648
2649 *p = 0;
2650 cmd = b_head(&check->bi);
2651
2652 while (*cmd) {
2653 /* look for next word */
2654 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
2655 cmd++;
2656 continue;
2657 }
2658
2659 if (*cmd == '#') {
2660 /* this is the beginning of a health status description,
2661 * skip the sharp and blanks.
2662 */
2663 cmd++;
2664 while (*cmd == '\t' || *cmd == ' ')
2665 cmd++;
2666 break;
2667 }
2668
2669 /* find the end of the word so that we have a null-terminated
2670 * word between <cmd> and <p>.
2671 */
2672 p = cmd + 1;
2673 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
2674 p++;
2675 if (*p)
2676 *p++ = 0;
2677
2678 /* first, health statuses */
2679 if (strcasecmp(cmd, "up") == 0) {
2680 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
2681 status = HCHK_STATUS_L7OKD;
2682 hs = cmd;
2683 }
2684 else if (strcasecmp(cmd, "down") == 0) {
2685 check->server->check.health = 0;
2686 status = HCHK_STATUS_L7STS;
2687 hs = cmd;
2688 }
2689 else if (strcasecmp(cmd, "stopped") == 0) {
2690 check->server->check.health = 0;
2691 status = HCHK_STATUS_L7STS;
2692 hs = cmd;
2693 }
2694 else if (strcasecmp(cmd, "fail") == 0) {
2695 check->server->check.health = 0;
2696 status = HCHK_STATUS_L7STS;
2697 hs = cmd;
2698 }
2699 /* admin statuses */
2700 else if (strcasecmp(cmd, "ready") == 0) {
2701 as = cmd;
2702 }
2703 else if (strcasecmp(cmd, "drain") == 0) {
2704 as = cmd;
2705 }
2706 else if (strcasecmp(cmd, "maint") == 0) {
2707 as = cmd;
2708 }
2709 /* try to parse a weight here and keep the last one */
2710 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
2711 ps = cmd;
2712 }
2713 /* try to parse a maxconn here */
2714 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
2715 cs = cmd;
2716 }
2717 else {
2718 /* keep a copy of the first error */
2719 if (!err)
2720 err = cmd;
2721 }
2722 /* skip to next word */
2723 cmd = p;
2724 }
2725 /* here, cmd points either to \0 or to the beginning of a
2726 * description. Skip possible leading spaces.
2727 */
2728 while (*cmd == ' ' || *cmd == '\n')
2729 cmd++;
2730
2731 /* First, update the admin status so that we avoid sending other
2732 * possibly useless warnings and can also update the health if
2733 * present after going back up.
2734 */
2735 if (as) {
2736 if (strcasecmp(as, "drain") == 0)
2737 srv_adm_set_drain(check->server);
2738 else if (strcasecmp(as, "maint") == 0)
2739 srv_adm_set_maint(check->server);
2740 else
2741 srv_adm_set_ready(check->server);
2742 }
2743
2744 /* now change weights */
2745 if (ps) {
2746 const char *msg;
2747
2748 msg = server_parse_weight_change_request(check->server, ps);
2749 if (!wrn || !*wrn)
2750 wrn = msg;
2751 }
2752
2753 if (cs) {
2754 const char *msg;
2755
2756 cs += strlen("maxconn:");
2757
2758 msg = server_parse_maxconn_change_request(check->server, cs);
2759 if (!wrn || !*wrn)
2760 wrn = msg;
2761 }
2762
2763 /* and finally health status */
2764 if (hs) {
2765 /* We'll report some of the warnings and errors we have
2766 * here. Down reports are critical, we leave them untouched.
2767 * Lack of report, or report of 'UP' leaves the room for
2768 * ERR first, then WARN.
2769 */
2770 const char *msg = cmd;
2771 struct buffer *t;
2772
2773 if (!*msg || status == HCHK_STATUS_L7OKD) {
2774 if (err && *err)
2775 msg = err;
2776 else if (wrn && *wrn)
2777 msg = wrn;
2778 }
2779
2780 t = get_trash_chunk();
2781 chunk_printf(t, "via agent : %s%s%s%s",
2782 hs, *msg ? " (" : "",
2783 msg, *msg ? ")" : "");
2784 set_server_check_status(check, status, t->area);
2785 }
2786 else if (err && *err) {
2787 /* No status change but we'd like to report something odd.
2788 * Just report the current state and copy the message.
2789 */
2790 chunk_printf(&trash, "agent reports an error : %s", err);
2791 set_server_check_status(check, status/*check->status*/, trash.area);
2792 }
2793 else if (wrn && *wrn) {
2794 /* No status change but we'd like to report something odd.
2795 * Just report the current state and copy the message.
2796 */
2797 chunk_printf(&trash, "agent warns : %s", wrn);
2798 set_server_check_status(check, status/*check->status*/, trash.area);
2799 }
2800 else
2801 set_server_check_status(check, status, NULL);
2802
2803 out:
2804 return ret;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002805
2806 wait_more_data:
2807 ret = TCPCHK_EVAL_WAIT;
2808 goto out;
2809}
2810
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002811/* Evaluate a TCPCHK_ACT_CONNECT rule. It returns 1 to evaluate the next rule, 0
2812 * to wait and -1 to stop the check. */
2813static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002814{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002815 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2816 struct tcpcheck_connect *connect = &rule->connect;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01002817 struct proxy *proxy = check->proxy;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002818 struct server *s = check->server;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002819 struct task *t = check->task;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002820 struct conn_stream *cs;
2821 struct connection *conn = NULL;
2822 struct protocol *proto;
2823 struct xprt_ops *xprt;
Christopher Faulet5c288742020-03-31 08:15:58 +02002824 int status, port;
Willy Tarreauef953952014-10-02 14:30:14 +02002825
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002826 /* For a connect action we'll create a new connection. We may also have
2827 * to kill a previous one. But we don't want to leave *without* a
2828 * connection if we came here from the connection layer, hence with a
2829 * connection. Thus we'll proceed in the following order :
2830 * 1: close but not release previous connection (handled by the caller)
2831 * 2: try to get a new connection
2832 * 3: release and replace the old one on success
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002833 */
Willy Tarreau449f9522015-05-13 15:39:48 +02002834
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002835 /* 2- prepare new connection */
2836 cs = cs_new(NULL);
2837 if (!cs) {
2838 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
2839 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002840 if (rule->comment)
2841 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002842 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2843 ret = TCPCHK_EVAL_STOP;
Christopher Fauletb6102852017-11-28 10:06:29 +01002844 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002845 }
2846
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002847 /* 3- release and replace the old one on success */
2848 if (check->cs) {
2849 if (check->wait_list.events)
2850 cs->conn->xprt->unsubscribe(cs->conn, cs->conn->xprt_ctx,
2851 check->wait_list.events, &check->wait_list);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002852
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002853 /* We may have been scheduled to run, and the I/O handler
2854 * expects to have a cs, so remove the tasklet
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002855 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002856 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
2857 cs_destroy(check->cs);
2858 }
Willy Tarreaudeccd112018-06-14 18:38:55 +02002859
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002860 tasklet_set_tid(check->wait_list.tasklet, tid);
Willy Tarreauabca5b62013-12-06 14:19:25 +01002861
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002862 check->cs = cs;
2863 conn = cs->conn;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002864
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002865 /* Maybe there were an older connection we were waiting on */
2866 check->wait_list.events = 0;
2867 conn->target = s ? &s->obj_type : &proxy->obj_type;
Willy Tarreauf3d34822014-12-08 12:11:28 +01002868
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002869 /* no client address */
2870 if (!sockaddr_alloc(&conn->dst)) {
2871 status = SF_ERR_RESOURCE;
2872 goto fail_check;
2873 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002874
Christopher Faulet5c288742020-03-31 08:15:58 +02002875 /* connect to the connect rule addr if specified, otherwise the check
2876 * addr if specified on the server. otherwise, use the server addr
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002877 */
Christopher Faulet5c288742020-03-31 08:15:58 +02002878 *conn->dst = (is_addr(&connect->addr)
2879 ? connect->addr
2880 : (is_addr(&check->addr) ? check->addr : s->addr));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002881 proto = protocol_by_family(conn->dst->ss_family);
Willy Tarreau00149122017-10-04 18:05:01 +02002882
Christopher Faulet5c288742020-03-31 08:15:58 +02002883 port = 0;
2884 if (!port && connect->port)
2885 port = connect->port;
Christopher Fauletb7d30092020-03-30 15:19:03 +02002886 if (!port && connect->port_expr) {
2887 struct sample *smp;
2888
2889 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
2890 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
2891 connect->port_expr, SMP_T_SINT);
2892 if (smp)
2893 port = smp->data.u.sint;
2894 }
Christopher Faulet5c288742020-03-31 08:15:58 +02002895 if (!port && is_inet_addr(&connect->addr))
2896 port = get_host_port(&connect->addr);
2897 if (!port && check->port)
2898 port = check->port;
2899 if (!port && is_inet_addr(&check->addr))
2900 port = get_host_port(&check->addr);
2901 if (!port)
2902 port = s->svc_port;
2903 set_host_port(conn->dst, port);
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002904
Christopher Fauletbb591a12020-04-01 16:52:17 +02002905 xprt = ((connect->options & TCPCHK_OPT_SSL)
2906 ? xprt_get(XPRT_SSL)
2907 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Willy Tarreau00149122017-10-04 18:05:01 +02002908
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002909 conn_prepare(conn, proto, xprt);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002910 if (conn_install_mux(conn, &mux_pt_ops, cs, proxy, check->sess) < 0) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002911 status = SF_ERR_RESOURCE;
2912 goto fail_check;
2913 }
2914 cs_attach(cs, check, &check_conn_cb);
Willy Tarreau00149122017-10-04 18:05:01 +02002915
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002916 status = SF_ERR_INTERNAL;
2917 if (proto && proto->connect) {
2918 struct tcpcheck_rule *next;
2919 int flags = CONNECT_HAS_DATA;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02002920
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002921 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
2922 if (!next || next->action != TCPCHK_ACT_EXPECT)
2923 flags |= CONNECT_DELACK_ALWAYS;
2924 status = proto->connect(conn, flags);
2925 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002926
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002927#ifdef USE_OPENSSL
Christopher Fauletbb591a12020-04-01 16:52:17 +02002928 if (status == SF_ERR_NONE) {
2929 if (connect->sni)
2930 ssl_sock_set_servername(conn, connect->sni);
2931 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
2932 ssl_sock_set_servername(conn, s->check.sni);
2933
2934 if (connect->alpn)
2935 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
2936 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
2937 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002938 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02002939#endif
Christopher Fauletbb591a12020-04-01 16:52:17 +02002940 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
2941 conn->send_proxy_ofs = 1;
2942 conn->flags |= CO_FL_SOCKS4;
2943 }
2944 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
2945 conn->send_proxy_ofs = 1;
2946 conn->flags |= CO_FL_SOCKS4;
2947 }
2948
2949 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
2950 conn->send_proxy_ofs = 1;
2951 conn->flags |= CO_FL_SEND_PROXY;
2952 }
2953 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
2954 conn->send_proxy_ofs = 1;
2955 conn->flags |= CO_FL_SEND_PROXY;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002956 }
Willy Tarreauca79f592019-07-17 19:04:47 +02002957
Christopher Fauletbb591a12020-04-01 16:52:17 +02002958 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
2959 /* Some servers don't like reset on close */
2960 fdtab[cs->conn->handle.fd].linger_risk = 0;
2961 }
2962
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002963 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
2964 if (xprt_add_hs(conn) < 0)
2965 status = SF_ERR_RESOURCE;
2966 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002967
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002968 fail_check:
2969 /* It can return one of :
2970 * - SF_ERR_NONE if everything's OK
2971 * - SF_ERR_SRVTO if there are no more servers
2972 * - SF_ERR_SRVCL if the connection was refused by the server
2973 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
2974 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2975 * - SF_ERR_INTERNAL for any other purely internal errors
2976 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2977 * Note that we try to prevent the network stack from sending the ACK during the
2978 * connect() when a pure TCP check is used (without PROXY protocol).
2979 */
2980 switch (status) {
2981 case SF_ERR_NONE:
2982 /* we allow up to min(inter, timeout.connect) for a connection
2983 * to establish but only when timeout.check is set as it may be
2984 * to short for a full check otherwise
2985 */
2986 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002987
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002988 if (proxy->timeout.check && proxy->timeout.connect) {
2989 int t_con = tick_add(now_ms, proxy->timeout.connect);
2990 t->expire = tick_first(t->expire, t_con);
2991 }
2992 break;
2993 case SF_ERR_SRVTO: /* ETIMEDOUT */
2994 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
2995 chunk_printf(&trash, "TCPCHK error establishing connection at step %d: %s",
2996 tcpcheck_get_step_id(check, rule), strerror(errno));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002997 if (rule->comment)
2998 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002999 set_server_check_status(check, HCHK_STATUS_L4CON, trash.area);
3000 ret = TCPCHK_EVAL_STOP;
3001 goto out;
3002 case SF_ERR_PRXCOND:
3003 case SF_ERR_RESOURCE:
3004 case SF_ERR_INTERNAL:
3005 chunk_printf(&trash, "TCPCHK error establishing connection at step %d",
3006 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003007 if (rule->comment)
3008 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003009 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3010 ret = TCPCHK_EVAL_STOP;
3011 goto out;
3012 }
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003013
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003014 /* don't do anything until the connection is established */
3015 if (conn->flags & CO_FL_WAIT_XPRT) {
3016 ret = TCPCHK_EVAL_WAIT;
3017 goto out;
3018 }
Willy Tarreaube373152018-09-06 11:45:30 +02003019
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003020 out:
3021 if (conn && check->result == CHK_RES_FAILED)
3022 conn->flags |= CO_FL_ERROR;
3023 return ret;
3024}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02003025
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003026/* Evaluate a TCPCHK_ACT_SEND rule. It returns 1 to evaluate the next rule, 0
3027 * to wait and -1 to stop the check. */
3028static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
3029{
3030 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3031 struct tcpcheck_send *send = &rule->send;
3032 struct conn_stream *cs = check->cs;
3033 struct connection *conn = cs_conn(cs);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003034 struct buffer *tmp = NULL;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003035
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003036 /* reset the read & write buffer */
3037 b_reset(&check->bi);
3038 b_reset(&check->bo);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01003039
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003040 switch (send->type) {
3041 case TCPCHK_SEND_STRING:
3042 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003043 if (istlen(send->data) >= b_size(&check->bo)) {
3044 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
3045 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
3046 tcpcheck_get_step_id(check, rule));
3047 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3048 ret = TCPCHK_EVAL_STOP;
3049 goto out;
3050 }
3051 b_putist(&check->bo, send->data);
3052 break;
3053 case TCPCHK_SEND_STRING_LF:
3054 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
3055 if (!b_data(&check->bo))
3056 goto out;
3057 break;
3058 case TCPCHK_SEND_BINARY_LF:
3059 tmp = alloc_trash_chunk();
3060 if (!tmp)
3061 goto error_lf;
3062 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
3063 if (!b_data(tmp))
3064 goto out;
3065 tmp->area[tmp->data] = '\0';
3066 b_set_data(&check->bo, b_size(&check->bo));
3067 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
3068 goto error_lf;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003069 break;
3070 case TCPCHK_SEND_UNDEF:
3071 /* Should never happen. */
3072 ret = TCPCHK_EVAL_STOP;
3073 goto out;
3074 };
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003075
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003076 if (conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0) <= 0) {
3077 ret = TCPCHK_EVAL_WAIT;
3078 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))
3079 ret = TCPCHK_EVAL_STOP;
3080 goto out;
3081 }
3082 if (b_data(&check->bo)) {
3083 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3084 ret = TCPCHK_EVAL_WAIT;
3085 goto out;
3086 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003087
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003088 out:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003089 free_trash_chunk(tmp);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003090 return ret;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003091
3092 error_lf:
3093 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
3094 tcpcheck_get_step_id(check, rule));
3095 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3096 ret = TCPCHK_EVAL_STOP;
3097 goto out;
3098
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003099}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01003100
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003101/* Evaluate a TCPCHK_ACT_EXPECT rule. It returns 1 to evaluate the next rule, 0
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003102 * to wait and -1 to stop the check.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003103 */
3104static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
3105{
3106 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Fauletec07e382020-04-07 14:56:26 +02003107 struct tcpcheck_expect *expect = &rule->expect;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003108 struct buffer *msg = NULL;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003109 int match;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003110
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003111 /* The current expect might need more data than the previous one, check again
3112 * that the minimum amount data required to match is respected.
3113 */
3114 if (!last_read) {
3115 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
3116 (b_data(&check->bi) < expect->length)) {
3117 ret = TCPCHK_EVAL_WAIT;
3118 goto out;
3119 }
3120 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
3121 ret = TCPCHK_EVAL_WAIT;
3122 goto out;
3123 }
3124 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003125
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003126 /* Make GCC happy ; initialize match to a failure state. */
3127 match = expect->inverse;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003128
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003129 switch (expect->type) {
3130 case TCPCHK_EXPECT_STRING:
3131 case TCPCHK_EXPECT_BINARY:
3132 match = my_memmem(b_head(&check->bi), b_data(&check->bi), expect->string, expect->length) != NULL;
3133 break;
3134 case TCPCHK_EXPECT_REGEX:
3135 if (expect->with_capture)
3136 match = regex_exec_match2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1),
3137 MAX_MATCH, pmatch, 0);
3138 else
3139 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
3140 break;
Willy Tarreau7df8ca62019-07-15 10:57:51 +02003141
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003142 case TCPCHK_EXPECT_REGEX_BINARY:
3143 chunk_reset(&trash);
3144 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
3145 if (expect->with_capture)
3146 match = regex_exec_match2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1),
3147 MAX_MATCH, pmatch, 0);
3148 else
3149 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
3150 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003151 case TCPCHK_EXPECT_CUSTOM:
3152 if (expect->custom)
3153 ret = expect->custom(check, rule, last_read);
3154 goto out;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003155 case TCPCHK_EXPECT_UNDEF:
3156 /* Should never happen. */
3157 ret = TCPCHK_EVAL_STOP;
3158 goto out;
3159 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003160
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003161
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003162 /* Wait for more data on mismatch only if no minimum is defined (-1),
3163 * otherwise the absence of match is already conclusive.
3164 */
3165 if (!match && !last_read && (expect->min_recv == -1)) {
3166 ret = TCPCHK_EVAL_WAIT;
3167 goto out;
3168 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003169
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003170 /* Result as expected, next rule. */
3171 if (match ^ expect->inverse)
3172 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003173
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01003174
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003175 /* From this point on, we matched something we did not want, this is an error state. */
3176 ret = TCPCHK_EVAL_STOP;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003177 msg = alloc_trash_chunk();
Christopher Faulet206368d2020-04-03 14:51:06 +02003178 if (msg)
3179 tcpcheck_onerror_message(msg, check, rule, match, ist(NULL));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003180 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
Christopher Faulet206368d2020-04-03 14:51:06 +02003181 free_trash_chunk(msg);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003182 ret = TCPCHK_EVAL_STOP;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003183
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003184 out:
3185 return ret;
3186}
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003187
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003188/* Evaluate a TCPCHK_ACT_ACTION_KW rule. It returns 1 to evaluate the next rule, 0
3189 * to wait and -1 to stop the check.
3190 */
3191static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
3192{
3193 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3194 struct act_rule *act_rule;
3195 enum act_return act_ret;
3196
3197 act_rule =rule->action_kw.rule;
3198 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
3199 if (act_ret != ACT_RET_CONT) {
3200 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
3201 tcpcheck_get_step_id(check, rule));
3202 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3203 ret = TCPCHK_EVAL_STOP;
3204 }
3205
3206 return ret;
3207}
3208
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003209/* proceed with next steps for the TCP checks <check>. Note that this is called
3210 * both from the connection's wake() callback and from the check scheduling task.
3211 * It returns 0 on normal cases, or <0 if a close() has happened on an existing
3212 * connection, presenting the risk of an fd replacement.
3213 *
3214 * Please do NOT place any return statement in this function and only leave
3215 * via the out_end_tcpcheck label after setting retcode.
3216 */
3217static int tcpcheck_main(struct check *check)
3218{
3219 struct tcpcheck_rule *rule;
3220 struct conn_stream *cs = check->cs;
3221 struct connection *conn = cs_conn(cs);
3222 int must_read = 1, last_read = 0;
3223 int ret, retcode = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003224
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003225 /* here, we know that the check is complete or that it failed */
3226 if (check->result != CHK_RES_UNKNOWN)
3227 goto out_end_tcpcheck;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003228
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003229 /* 1- check for connection error, if any */
3230 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3231 goto out_end_tcpcheck;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003232
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003233 /* 2- check if we are waiting for the connection establishment. It only
3234 * happens during TCPCHK_ACT_CONNECT. */
3235 if (conn && (conn->flags & CO_FL_WAIT_XPRT))
3236 goto out;
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003237
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003238 /* 3- check for pending outgoing data. It only happens during TCPCHK_ACT_SEND. */
3239 if (conn && b_data(&check->bo)) {
3240 ret = conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
3241 if (ret <= 0) {
3242 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3243 goto out_end_tcpcheck;
3244 goto out;
3245 }
3246 if (b_data(&check->bo)) {
3247 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3248 goto out;
3249 }
3250 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003251
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003252 /* Now evaluate the tcp-check rules */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003253
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003254 /* If check->current_step is defined, we are in resume condition. For
3255 * TCPCHK_ACT_CONNECT and TCPCHK_ACT_SEND rules, we must go to the next
3256 * rule before resuming the evaluation. For TCPCHK_ACT_EXPECT, we
3257 * re-evaluate the current rule. Others cannot yield.
3258 */
3259 if (check->current_step) {
3260 if (check->current_step->action == TCPCHK_ACT_CONNECT ||
3261 check->current_step->action == TCPCHK_ACT_SEND)
3262 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
3263 else
3264 rule = check->current_step;
3265 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003266 else {
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003267 struct tcpcheck_var *var;
3268
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003269 /* First evaluation, create a session */
Gaetan Rivet13a50432020-02-21 18:13:44 +01003270 check->sess = session_new(&checks_fe, NULL, (check->server ? &check->server->obj_type : NULL));
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003271 if (!check->sess) {
3272 chunk_printf(&trash, "TCPCHK error allocating check session");
3273 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3274 goto out_end_tcpcheck;
3275 }
Gaetan Rivet13a50432020-02-21 18:13:44 +01003276 vars_init(&check->vars, SCOPE_CHECK);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003277 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003278
3279 /* Preset tcp-check variables */
3280 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
3281 struct sample smp;
3282
3283 memset(&smp, 0, sizeof(smp));
3284 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
3285 smp.data = var->data;
3286 vars_set_by_name_ifexist(var->name.ptr, var->name.len, &smp);
3287 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003288 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003289
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003290 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003291 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003292
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003293 switch (rule->action) {
3294 case TCPCHK_ACT_CONNECT:
3295 check->current_step = rule;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003296
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003297 /* close but not release yet previous connection */
3298 if (check->cs) {
3299 cs_close(check->cs);
3300 retcode = -1; /* do not reuse the fd in the caller! */
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003301 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003302 eval_ret = tcpcheck_eval_connect(check, rule);
3303 must_read = 1; last_read = 0;
3304 break;
3305 case TCPCHK_ACT_SEND:
3306 check->current_step = rule;
3307 eval_ret = tcpcheck_eval_send(check, rule);
3308 must_read = 1;
3309 break;
3310 case TCPCHK_ACT_EXPECT:
3311 check->current_step = rule;
3312 if (must_read) {
3313 if (check->proxy->timeout.check)
3314 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003315
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003316 /* If we already subscribed, then we tried to received and
3317 * failed, so there's no point trying again.
3318 */
3319 if (check->wait_list.events & SUB_RETRY_RECV)
3320 goto out;
3321 if (conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0) <= 0) {
3322 if (conn->flags & (CO_FL_ERROR|CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
3323 last_read = 1;
3324 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
3325 /* Report network errors only if we got no other data. Otherwise
3326 * we'll let the upper layers decide whether the response is OK
3327 * or not. It is very common that an RST sent by the server is
3328 * reported as an error just after the last data chunk.
3329 */
3330 goto out_end_tcpcheck;
3331 }
3332 }
3333 else {
3334 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
3335 goto out;
3336 }
3337 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003338
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003339 /* buffer full, don't wait for more data */
3340 if (b_full(&check->bi))
3341 last_read = 1;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003342
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003343 /* Check that response body is not empty... */
3344 if (!b_data(&check->bi)) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003345 if (!last_read)
3346 goto out;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003347
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003348 /* empty response */
3349 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
3350 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003351 if (rule->comment)
3352 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003353 set_server_check_status(check, rule->expect.err_status, trash.area);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003354 ret = -1;
3355 goto out_end_tcpcheck;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003356 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003357 must_read = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003358 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003359
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003360 eval_ret = tcpcheck_eval_expect(check, rule, last_read);
3361 if (eval_ret == TCPCHK_EVAL_WAIT) {
3362 check->current_step = rule->expect.head;
3363 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003364 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003365 break;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003366 case TCPCHK_ACT_ACTION_KW:
3367 /* Don't update the current step */
3368 eval_ret = tcpcheck_eval_action_kw(check, rule);
3369 break;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003370 default:
3371 /* Otherwise, just go to the next one and don't update
3372 * the current step
3373 */
3374 eval_ret = TCPCHK_EVAL_CONTINUE;
3375 break;
3376 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003377
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003378 switch (eval_ret) {
3379 case TCPCHK_EVAL_CONTINUE:
3380 break;
3381 case TCPCHK_EVAL_WAIT:
3382 goto out;
3383 case TCPCHK_EVAL_STOP:
3384 goto out_end_tcpcheck;
Baptiste Assmann248f1172018-03-01 21:49:01 +01003385 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003386 }
3387
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003388 /* All rules was evaluated */
Christopher Fauletdf38f882020-04-07 16:04:38 +02003389 if (check->current_step) {
3390 rule = check->current_step;
3391
3392 if (rule->action == TCPCHK_ACT_EXPECT) {
3393 struct buffer *msg = alloc_trash_chunk();
3394
3395 if (msg)
3396 tcpcheck_onsuccess_message(msg, check, rule, ist(NULL));
3397 set_server_check_status(check, rule->expect.ok_status,
3398 (msg ? b_head(msg) : "(tcp-check)"));
3399 free_trash_chunk(msg);
3400 }
3401 else if (rule->action == TCPCHK_ACT_CONNECT) {
3402 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
3403 enum healthcheck_status status = ((conn && ssl_sock_is_ssl(conn)) ? HCHK_STATUS_L6OK : HCHK_STATUS_L4OK);
3404
3405 set_server_check_status(check, status, msg);
3406 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003407 }
Christopher Fauletec07e382020-04-07 14:56:26 +02003408 else
3409 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003410
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003411 out_end_tcpcheck:
Willy Tarreauef91c932019-07-23 14:37:47 +02003412 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003413 chk_report_conn_err(check, errno, 0);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003414
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003415 /* cleanup before leaving */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003416 check->current_step = NULL;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003417 if (check->sess != NULL) {
Gaetan Rivet13a50432020-02-21 18:13:44 +01003418 vars_prune(&check->vars, check->sess, NULL);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003419 session_free(check->sess);
3420 check->sess = NULL;
3421 }
3422 out:
3423 return retcode;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003424}
3425
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003426static const char *init_check(struct check *check, int type)
Simon Hormanb1900d52015-01-30 11:22:54 +09003427{
3428 check->type = type;
3429
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003430 b_reset(&check->bi); check->bi.size = global.tune.chksize;
3431 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Simon Hormanb1900d52015-01-30 11:22:54 +09003432
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003433 check->bi.area = calloc(check->bi.size, sizeof(char));
3434 check->bo.area = calloc(check->bo.size, sizeof(char));
3435
3436 if (!check->bi.area || !check->bo.area)
Simon Hormanb1900d52015-01-30 11:22:54 +09003437 return "out of memory while allocating check buffer";
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003438
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003439 check->wait_list.tasklet = tasklet_new();
3440 if (!check->wait_list.tasklet)
Ilya Shipitsind4259502020-04-08 01:07:56 +05003441 return "out of memory while allocating check tasklet";
Willy Tarreau4f6516d2018-12-19 13:59:17 +01003442 check->wait_list.events = 0;
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003443 check->wait_list.tasklet->process = event_srv_chk_io;
3444 check->wait_list.tasklet->context = check;
Simon Hormanb1900d52015-01-30 11:22:54 +09003445 return NULL;
3446}
3447
Simon Hormanbfb5d332015-01-30 11:22:55 +09003448void free_check(struct check *check)
3449{
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003450 task_destroy(check->task);
3451 if (check->wait_list.tasklet)
3452 tasklet_free(check->wait_list.tasklet);
3453
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003454 free(check->bi.area);
3455 free(check->bo.area);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003456 if (check->cs) {
3457 free(check->cs->conn);
3458 check->cs->conn = NULL;
3459 cs_free(check->cs);
3460 check->cs = NULL;
3461 }
Simon Hormanbfb5d332015-01-30 11:22:55 +09003462}
3463
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003464static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
3465{
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003466 struct logformat_node *lf, *lfb;
3467
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003468 if (!rule)
3469 return;
3470
3471 free(rule->comment);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003472 switch (rule->action) {
3473 case TCPCHK_ACT_SEND:
3474 switch (rule->send.type) {
3475 case TCPCHK_SEND_STRING:
3476 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003477 free(rule->send.data.ptr);
3478 break;
3479 case TCPCHK_SEND_STRING_LF:
3480 case TCPCHK_SEND_BINARY_LF:
3481 list_for_each_entry_safe(lf, lfb, &rule->send.fmt, list) {
3482 LIST_DEL(&lf->list);
3483 release_sample_expr(lf->expr);
3484 free(lf->arg);
3485 free(lf);
3486 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003487 break;
3488 case TCPCHK_SEND_UNDEF:
3489 break;
3490 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003491 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003492 case TCPCHK_ACT_EXPECT:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003493 list_for_each_entry_safe(lf, lfb, &rule->expect.onerror_fmt, list) {
3494 LIST_DEL(&lf->list);
3495 release_sample_expr(lf->expr);
3496 free(lf->arg);
3497 free(lf);
3498 }
3499 list_for_each_entry_safe(lf, lfb, &rule->expect.onsuccess_fmt, list) {
3500 LIST_DEL(&lf->list);
3501 release_sample_expr(lf->expr);
3502 free(lf->arg);
3503 free(lf);
3504 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003505 release_sample_expr(rule->expect.status_expr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003506 switch (rule->expect.type) {
3507 case TCPCHK_EXPECT_STRING:
3508 case TCPCHK_EXPECT_BINARY:
3509 free(rule->expect.string);
3510 break;
3511 case TCPCHK_EXPECT_REGEX:
3512 case TCPCHK_EXPECT_REGEX_BINARY:
3513 regex_free(rule->expect.regex);
3514 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003515 case TCPCHK_EXPECT_CUSTOM:
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003516 case TCPCHK_EXPECT_UNDEF:
3517 break;
3518 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003519 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003520 case TCPCHK_ACT_CONNECT:
Christopher Faulet79b31d42020-03-30 13:00:05 +02003521 free(rule->connect.sni);
Christopher Faulet98572322020-03-30 13:16:44 +02003522 free(rule->connect.alpn);
Christopher Fauletb7d30092020-03-30 15:19:03 +02003523 release_sample_expr(rule->connect.port_expr);
Christopher Faulet79b31d42020-03-30 13:00:05 +02003524 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003525 case TCPCHK_ACT_COMMENT:
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003526 break;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01003527 case TCPCHK_ACT_ACTION_KW:
3528 free(rule->action_kw.rule);
3529 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003530 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003531
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003532 if (in_pool)
3533 pool_free(pool_head_tcpcheck_rule, rule);
3534 else
3535 free(rule);
3536}
3537
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003538
Christopher Fauletce355072020-04-02 11:44:39 +02003539static struct tcpcheck_var *tcpcheck_var_create(const char *name)
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003540{
3541 struct tcpcheck_var *var = NULL;
3542
3543 var = calloc(1, sizeof(*var));
3544 if (var == NULL)
3545 return NULL;
3546
3547 var->name = ist2(strdup(name), strlen(name));
3548 if (var->name.ptr == NULL) {
3549 free(var);
3550 return NULL;
3551 }
3552
3553 LIST_INIT(&var->list);
3554 return var;
3555}
3556
3557static void tcpcheck_var_release(struct tcpcheck_var *var)
3558{
3559 if (!var)
3560 return;
3561
3562 free(var->name.ptr);
3563 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
3564 free(var->data.u.str.area);
3565 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
3566 free(var->data.u.meth.str.area);
3567 free(var);
3568}
3569
3570int dup_tcpcheck_vars(struct list *dst, struct list *src)
3571{
3572 struct tcpcheck_var *var, *new = NULL;
3573
3574 list_for_each_entry(var, src, list) {
3575 new = tcpcheck_var_create(var->name.ptr);
3576 if (!new)
3577 goto error;
3578 new->data.type = var->data.type;
3579 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
3580 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3581 goto error;
3582 if (var->data.type == SMP_T_STR)
3583 new->data.u.str.area[new->data.u.str.data] = 0;
3584 }
3585 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
3586 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3587 goto error;
3588 new->data.u.str.area[new->data.u.str.data] = 0;
3589 new->data.u.meth.meth = var->data.u.meth.meth;
3590 }
3591 else
3592 new->data.u = var->data.u;
3593 LIST_ADDQ(dst, &new->list);
3594 }
3595 return 1;
3596
3597 error:
3598 free(new);
3599 return 0;
3600}
3601
3602static void free_tcpcheck_vars(struct list *vars)
3603{
3604 struct tcpcheck_var *var, *back;
3605
3606 list_for_each_entry_safe(var, back, vars, list) {
3607 LIST_DEL(&var->list);
3608 tcpcheck_var_release(var);
3609 }
3610}
3611
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003612void email_alert_free(struct email_alert *alert)
3613{
3614 struct tcpcheck_rule *rule, *back;
3615
3616 if (!alert)
3617 return;
3618
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003619 if (alert->rules.list) {
3620 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
3621 LIST_DEL(&rule->list);
3622 free_tcpcheck(rule, 1);
3623 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003624 free_tcpcheck_vars(&alert->rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003625 free(alert->rules.list);
3626 alert->rules.list = NULL;
Christopher Fauletde1a75b2017-10-23 15:38:19 +02003627 }
Willy Tarreaubafbe012017-11-24 17:34:44 +01003628 pool_free(pool_head_email_alert, alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003629}
3630
Olivier Houchard9f6af332018-05-25 14:04:04 +02003631static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003632{
Olivier Houchard9f6af332018-05-25 14:04:04 +02003633 struct check *check = context;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003634 struct email_alertq *q;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003635 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003636
3637 q = container_of(check, typeof(*q), check);
3638
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003639 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003640 while (1) {
3641 if (!(check->state & CHK_ST_ENABLED)) {
3642 if (LIST_ISEMPTY(&q->email_alerts)) {
3643 /* All alerts processed, queue the task */
3644 t->expire = TICK_ETERNITY;
3645 task_queue(t);
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003646 goto end;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003647 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003648
3649 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003650 LIST_DEL(&alert->list);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003651 t->expire = now_ms;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003652 check->tcpcheck_rules = &alert->rules;
Olivier Houchard0923fa42019-01-11 18:43:04 +01003653 check->status = HCHK_STATUS_INI;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003654 check->state |= CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003655 }
3656
Olivier Houchard9f6af332018-05-25 14:04:04 +02003657 process_chk(t, context, state);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003658 if (check->state & CHK_ST_INPROGRESS)
3659 break;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003660
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003661 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003662 email_alert_free(alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003663 check->tcpcheck_rules = NULL;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003664 check->server = NULL;
3665 check->state &= ~CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003666 }
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003667 end:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003668 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003669 return t;
3670}
3671
Christopher Faulet0108bb32017-10-20 21:34:32 +02003672/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
3673 *
3674 * The function returns 1 in success case, otherwise, it returns 0 and err is
3675 * filled.
3676 */
3677int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003678{
Christopher Faulet0108bb32017-10-20 21:34:32 +02003679 struct mailer *mailer;
3680 struct email_alertq *queues;
3681 const char *err_str;
3682 int i = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003683
Christopher Faulet0108bb32017-10-20 21:34:32 +02003684 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
3685 memprintf(err, "out of memory while allocating mailer alerts queues");
mildis5ab01cb2018-10-02 16:46:34 +02003686 goto fail_no_queue;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003687 }
3688
Christopher Faulet0108bb32017-10-20 21:34:32 +02003689 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
3690 struct email_alertq *q = &queues[i];
3691 struct check *check = &q->check;
3692 struct task *t;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003693
3694 LIST_INIT(&q->email_alerts);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003695 HA_SPIN_INIT(&q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003696 check->inter = mls->timeout.mail;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003697 check->rise = DEF_AGENT_RISETIME;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01003698 check->proxy = p;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003699 check->fall = DEF_AGENT_FALLTIME;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003700 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
3701 memprintf(err, "%s", err_str);
3702 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003703 }
3704
3705 check->xprt = mailer->xprt;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003706 check->addr = mailer->addr;
Christopher Fauletb797ae12018-03-27 15:35:35 +02003707 check->port = get_host_port(&mailer->addr);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003708
Emeric Brunc60def82017-09-27 14:59:38 +02003709 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003710 memprintf(err, "out of memory while allocating mailer alerts task");
3711 goto error;
3712 }
3713
3714 check->task = t;
3715 t->process = process_email_alert;
3716 t->context = check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003717
Christopher Faulet0108bb32017-10-20 21:34:32 +02003718 /* check this in one ms */
3719 t->expire = TICK_ETERNITY;
3720 check->start = now;
3721 task_queue(t);
3722 }
3723
3724 mls->users++;
3725 free(p->email_alert.mailers.name);
3726 p->email_alert.mailers.m = mls;
3727 p->email_alert.queues = queues;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003728 return 0;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003729
3730 error:
3731 for (i = 0; i < mls->count; i++) {
3732 struct email_alertq *q = &queues[i];
3733 struct check *check = &q->check;
3734
Christopher Faulet0108bb32017-10-20 21:34:32 +02003735 free_check(check);
3736 }
3737 free(queues);
mildis5ab01cb2018-10-02 16:46:34 +02003738 fail_no_queue:
Christopher Faulet0108bb32017-10-20 21:34:32 +02003739 return 1;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003740}
3741
3742
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003743static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003744{
Gaetan Rivet4038b942020-02-26 16:19:40 +01003745 struct tcpcheck_rule *tcpcheck, *prev_check;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003746 struct tcpcheck_expect *expect;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003747
Willy Tarreaubafbe012017-11-24 17:34:44 +01003748 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003749 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003750 memset(tcpcheck, 0, sizeof(*tcpcheck));
Gaetan Rivetb616add2020-02-07 15:37:17 +01003751 tcpcheck->action = TCPCHK_ACT_EXPECT;
3752
3753 expect = &tcpcheck->expect;
3754 expect->type = TCPCHK_EXPECT_STRING;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003755 LIST_INIT(&expect->onerror_fmt);
3756 LIST_INIT(&expect->onsuccess_fmt);
Christopher Fauletec07e382020-04-07 14:56:26 +02003757 expect->ok_status = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003758 expect->err_status = HCHK_STATUS_L7RSP;
3759 expect->tout_status = HCHK_STATUS_L7TOUT;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003760 expect->string = strdup(str);
3761 if (!expect->string) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003762 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003763 return 0;
3764 }
Gaetan Rivetb616add2020-02-07 15:37:17 +01003765 expect->length = strlen(expect->string);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003766
Gaetan Rivet4038b942020-02-26 16:19:40 +01003767 /* All tcp-check expect points back to the first inverse expect rule
3768 * in a chain of one or more expect rule, potentially itself.
3769 */
Gaetan Rivetb616add2020-02-07 15:37:17 +01003770 tcpcheck->expect.head = tcpcheck;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003771 list_for_each_entry_rev(prev_check, rules->list, list) {
Gaetan Rivet4038b942020-02-26 16:19:40 +01003772 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Gaetan Rivetb616add2020-02-07 15:37:17 +01003773 if (prev_check->expect.inverse)
3774 tcpcheck->expect.head = prev_check;
Gaetan Rivet4038b942020-02-26 16:19:40 +01003775 continue;
3776 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003777 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet4038b942020-02-26 16:19:40 +01003778 break;
3779 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003780 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003781 return 1;
3782}
3783
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003784static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003785{
3786 struct tcpcheck_rule *tcpcheck;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003787 struct tcpcheck_send *send;
Willy Tarreau64345aa2016-08-10 19:29:09 +02003788 const char *in;
3789 char *dst;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003790 int i;
3791
Willy Tarreaubafbe012017-11-24 17:34:44 +01003792 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003793 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003794 memset(tcpcheck, 0, sizeof(*tcpcheck));
3795 tcpcheck->action = TCPCHK_ACT_SEND;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003796
3797 send = &tcpcheck->send;
3798 send->type = TCPCHK_SEND_STRING;
3799
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003800 for (i = 0; strs[i]; i++)
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003801 send->data.len += strlen(strs[i]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003802
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003803 send->data.ptr = malloc(send->data.len + 1);
3804 if (!isttest(send->data)) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003805 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003806 return 0;
3807 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003808
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003809 dst = send->data.ptr;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003810 for (i = 0; strs[i]; i++)
Willy Tarreau64345aa2016-08-10 19:29:09 +02003811 for (in = strs[i]; (*dst = *in++); dst++);
3812 *dst = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003813
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003814 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003815 return 1;
3816}
3817
Christopher Faulet0108bb32017-10-20 21:34:32 +02003818static int enqueue_one_email_alert(struct proxy *p, struct server *s,
3819 struct email_alertq *q, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003820{
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003821 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003822 struct tcpcheck_rule *tcpcheck;
3823 struct check *check = &q->check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003824
Willy Tarreaubafbe012017-11-24 17:34:44 +01003825 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003826 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003827 LIST_INIT(&alert->list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003828 alert->rules.flags = 0;
3829 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
3830 if (!alert->rules.list)
3831 goto error;
3832 LIST_INIT(alert->rules.list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003833 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
Christopher Faulet0108bb32017-10-20 21:34:32 +02003834 alert->srv = s;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003835
Willy Tarreaubafbe012017-11-24 17:34:44 +01003836 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003837 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003838 memset(tcpcheck, 0, sizeof(*tcpcheck));
3839 tcpcheck->action = TCPCHK_ACT_CONNECT;
3840 tcpcheck->comment = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003841
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003842 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003843
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003844 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003845 goto error;
3846
3847 {
3848 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003849 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003850 goto error;
3851 }
3852
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003853 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003854 goto error;
3855
3856 {
3857 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003858 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003859 goto error;
3860 }
3861
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003862 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003863 goto error;
3864
3865 {
3866 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003867 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003868 goto error;
3869 }
3870
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003871 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003872 goto error;
3873
3874 {
3875 const char * const strs[2] = { "DATA\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003876 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003877 goto error;
3878 }
3879
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003880 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003881 goto error;
3882
3883 {
3884 struct tm tm;
3885 char datestr[48];
3886 const char * const strs[18] = {
Pieter Baauw5e0964e2016-02-13 16:27:35 +01003887 "From: ", p->email_alert.from, "\r\n",
3888 "To: ", p->email_alert.to, "\r\n",
3889 "Date: ", datestr, "\r\n",
3890 "Subject: [HAproxy Alert] ", msg, "\r\n",
3891 "\r\n",
3892 msg, "\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003893 "\r\n",
Pieter Baauwed35c372015-07-22 19:51:54 +02003894 ".\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003895 NULL
3896 };
3897
3898 get_localtime(date.tv_sec, &tm);
3899
3900 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
3901 goto error;
3902 }
3903
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003904 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003905 goto error;
3906 }
3907
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003908 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003909 goto error;
3910
3911 {
3912 const char * const strs[2] = { "QUIT\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003913 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003914 goto error;
3915 }
3916
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003917 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003918 goto error;
3919
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003920 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003921 task_wakeup(check->task, TASK_WOKEN_MSG);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003922 LIST_ADDQ(&q->email_alerts, &alert->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003923 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003924 return 1;
3925
3926error:
3927 email_alert_free(alert);
3928 return 0;
3929}
3930
Christopher Faulet0108bb32017-10-20 21:34:32 +02003931static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003932{
3933 int i;
3934 struct mailer *mailer;
3935
3936 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
3937 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003938 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003939 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003940 return;
3941 }
3942 }
3943
3944 return;
3945}
3946
3947/*
3948 * Send email alert if configured.
3949 */
Simon Horman64e34162015-02-06 11:11:57 +09003950void send_email_alert(struct server *s, int level, const char *format, ...)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003951{
3952 va_list argp;
3953 char buf[1024];
3954 int len;
3955 struct proxy *p = s->proxy;
3956
Christopher Faulet0108bb32017-10-20 21:34:32 +02003957 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003958 return;
3959
3960 va_start(argp, format);
3961 len = vsnprintf(buf, sizeof(buf), format, argp);
3962 va_end(argp);
3963
Thierry FOURNIER62c8a212017-02-09 12:19:27 +01003964 if (len < 0 || len >= sizeof(buf)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003965 ha_alert("Email alert [%s] could not format message\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003966 return;
3967 }
3968
Christopher Faulet0108bb32017-10-20 21:34:32 +02003969 enqueue_email_alert(p, s, buf);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003970}
3971
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003972/*
3973 * Return value:
3974 * the port to be used for the health check
3975 * 0 in case no port could be found for the check
3976 */
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003977static int srv_check_healthcheck_port(struct check *chk)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003978{
3979 int i = 0;
3980 struct server *srv = NULL;
3981
3982 srv = chk->server;
3983
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003984 /* by default, we use the health check port ocnfigured */
3985 if (chk->port > 0)
3986 return chk->port;
3987
3988 /* try to get the port from check_core.addr if check.port not set */
3989 i = get_host_port(&chk->addr);
3990 if (i > 0)
3991 return i;
3992
3993 /* try to get the port from server address */
3994 /* prevent MAPPORTS from working at this point, since checks could
3995 * not be performed in such case (MAPPORTS impose a relative ports
3996 * based on live traffic)
3997 */
3998 if (srv->flags & SRV_F_MAPPORTS)
3999 return 0;
Willy Tarreau04276f32017-01-06 17:41:29 +01004000
4001 i = srv->svc_port; /* by default */
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004002 if (i > 0)
4003 return i;
4004
4005 return 0;
4006}
4007
Willy Tarreau172f5ce2018-11-26 11:21:50 +01004008REGISTER_POST_CHECK(start_checks);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02004009
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004010static int check_proxy_tcpcheck(struct proxy *px)
4011{
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004012 struct tcpcheck_rule *chk, *back;
4013 char *comment = NULL;
4014 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004015 int ret = 0;
4016
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004017 if ((px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004018 goto out;
4019
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004020 if (!px->tcpcheck_rules.list) {
4021 px->tcpcheck_rules.list = calloc(1, sizeof(*px->tcpcheck_rules.list));
4022 if (!px->tcpcheck_rules.list) {
4023 ha_alert("config : proxy '%s': out of memory.\n", px->id);
4024 ret |= ERR_ALERT | ERR_FATAL;
4025 goto out;
4026 }
4027 LIST_INIT(px->tcpcheck_rules.list);
4028 }
4029
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004030 /* If there is no connect rule preceeding all send / expect rules, an
4031 * implicit one is inserted before all others
4032 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004033 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004034 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4035 chk = calloc(1, sizeof(*chk));
4036 if (!chk) {
4037 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4038 "(out of memory).\n", px->id);
4039 ret |= ERR_ALERT | ERR_FATAL;
4040 goto out;
4041 }
4042 chk->action = TCPCHK_ACT_CONNECT;
Christopher Fauletbb591a12020-04-01 16:52:17 +02004043 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004044 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004045 }
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004046
4047 /* Now remove comment rules */
4048 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4049 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4050 free(comment);
4051 comment = NULL;
4052 }
4053
4054 prev_action = chk->action;
4055 switch (chk->action) {
4056 case TCPCHK_ACT_COMMENT:
4057 free(comment);
4058 comment = chk->comment;
4059 LIST_DEL(&chk->list);
4060 free(chk);
4061 break;
4062 case TCPCHK_ACT_CONNECT:
4063 if (!chk->comment && comment)
4064 chk->comment = strdup(comment);
4065 /* fall though */
4066 case TCPCHK_ACT_ACTION_KW:
4067 free(comment);
4068 comment = NULL;
4069 break;
4070 case TCPCHK_ACT_SEND:
4071 case TCPCHK_ACT_EXPECT:
4072 if (!chk->comment && comment)
4073 chk->comment = strdup(comment);
4074 break;
4075 }
4076 }
4077 free(comment);
4078 comment = NULL;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004079
4080 out:
4081 return ret;
4082}
4083
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004084static int init_srv_check(struct server *srv)
4085{
4086 const char *err;
4087 struct tcpcheck_rule *r;
4088 int ret = 0;
4089
4090 if (!srv->do_check)
4091 goto out;
4092
4093
4094 /* If neither a port nor an addr was specified and no check transport
4095 * layer is forced, then the transport layer used by the checks is the
4096 * same as for the production traffic. Otherwise we use raw_sock by
4097 * default, unless one is specified.
4098 */
4099 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4100 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4101 srv->check.use_ssl = srv->use_ssl;
4102 srv->check.xprt = srv->xprt;
4103 }
4104 else if (srv->check.use_ssl == 1)
4105 srv->check.xprt = xprt_get(XPRT_SSL);
4106
4107 srv->check.send_proxy |= (srv->pp_opts);
4108 }
4109
4110 /* validate <srv> server health-check settings */
4111
4112 /* We need at least a service port, a check port or the first tcp-check
4113 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4114 */
4115 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4116 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4117 goto init;
4118
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004119 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004120 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4121 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4122 ret |= ERR_ALERT | ERR_ABORT;
4123 goto out;
4124 }
4125
4126 /* search the first action (connect / send / expect) in the list */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004127 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
Christopher Faulet5c288742020-03-31 08:15:58 +02004128 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004129 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4130 "nor tcp_check rule 'connect' with port information.\n",
4131 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4132 ret |= ERR_ALERT | ERR_ABORT;
4133 goto out;
4134 }
4135
4136 /* scan the tcp-check ruleset to ensure a port has been configured */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004137 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
Christopher Faulet5c288742020-03-31 08:15:58 +02004138 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004139 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4140 "and a tcp_check rule 'connect' with no port information.\n",
4141 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4142 ret |= ERR_ALERT | ERR_ABORT;
4143 goto out;
4144 }
4145 }
4146
4147 init:
4148 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4149 if (err) {
4150 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4151 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4152 ret |= ERR_ALERT | ERR_ABORT;
4153 goto out;
4154 }
4155 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4156 global.maxsock++;
4157
4158 out:
4159 return ret;
4160}
4161
4162static int init_srv_agent_check(struct server *srv)
4163{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004164 struct tcpcheck_rule *chk;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004165 const char *err;
4166 int ret = 0;
4167
4168 if (!srv->do_agent)
4169 goto out;
4170
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004171 /* If there is no connect rule preceeding all send / expect rules, an
4172 * implicit one is inserted before all others.
4173 */
4174 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4175 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4176 chk = calloc(1, sizeof(*chk));
4177 if (!chk) {
4178 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4179 " to agent-check for server '%s' (out of memory).\n",
4180 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4181 ret |= ERR_ALERT | ERR_FATAL;
4182 goto out;
4183 }
4184 chk->action = TCPCHK_ACT_CONNECT;
4185 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4186 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
4187 }
4188
4189
4190 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004191 if (err) {
4192 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4193 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4194 ret |= ERR_ALERT | ERR_ABORT;
4195 goto out;
4196 }
4197
4198 if (!srv->agent.inter)
4199 srv->agent.inter = srv->check.inter;
4200
4201 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4202 global.maxsock++;
4203
4204 out:
4205 return ret;
4206}
4207
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004208void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004209{
4210 struct tcpcheck_rule *chk, *back;
4211
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004212 if (!px->tcpcheck_rules.list || (px->tcpcheck_rules.flags & TCPCHK_RULES_SHARED))
4213 goto end;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004214
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004215 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004216 LIST_DEL(&chk->list);
4217 free_tcpcheck(chk, 0);
4218 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02004219 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004220 free(px->tcpcheck_rules.list);
4221
4222 end:
4223 px->tcpcheck_rules.flags = 0;
4224 px->tcpcheck_rules.list = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004225}
4226
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004227static void deinit_srv_check(struct server *srv)
4228{
Christopher Fauletce8111e2020-04-06 15:04:11 +02004229 if (srv->check.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004230 free_check(&srv->check);
Christopher Fauletce8111e2020-04-06 15:04:11 +02004231 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
4232 srv->do_check = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004233}
4234
4235
4236static void deinit_srv_agent_check(struct server *srv)
4237{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004238 if (srv->agent.tcpcheck_rules) {
4239 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
4240 free(srv->agent.tcpcheck_rules);
4241 srv->agent.tcpcheck_rules = NULL;
4242 }
4243
4244 if (srv->agent.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004245 free_check(&srv->agent);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004246
4247 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
4248 srv->do_agent = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004249}
4250
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004251static void deinit_tcpchecks()
4252{
4253 struct tcpcheck_ruleset *rs, *rsb;
4254 struct tcpcheck_rule *r, *rb;
4255
4256 list_for_each_entry_safe(rs, rsb, &tcpchecks_list, list) {
4257 LIST_DEL(&rs->list);
4258 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4259 LIST_DEL(&r->list);
4260 free_tcpcheck(r, 0);
4261 }
4262 free(rs->name);
4263 free(rs);
4264 }
4265}
4266
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004267
4268REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004269REGISTER_POST_SERVER_CHECK(init_srv_check);
4270REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
4271
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004272REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004273REGISTER_SERVER_DEINIT(deinit_srv_check);
4274REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004275REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004276
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004277/* extracts check payload at a fixed position and length */
4278static int
4279smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
4280{
4281 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
4282 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
4283 struct server *srv = (smp->sess ? objt_server(smp->sess->origin) : NULL);
4284 struct buffer *buf;
4285
4286 if (!srv || !srv->do_check)
4287 return 0;
4288
4289 buf = &srv->check.bi;
4290 if (buf_offset > b_data(buf))
4291 goto no_match;
4292 if (buf_offset + buf_size > b_data(buf))
4293 buf_size = 0;
4294
4295 /* init chunk as read only */
4296 smp->data.type = SMP_T_STR;
4297 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
4298 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
4299
4300 return 1;
4301
4302 no_match:
4303 smp->flags = 0;
4304 return 0;
4305}
4306
4307static struct sample_fetch_kw_list smp_kws = {ILH, {
4308 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
4309 { /* END */ },
4310}};
4311
4312INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
4313
4314
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004315struct action_kw_list tcp_check_keywords = {
4316 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
4317};
4318
4319/* Return the struct action_kw associated to a keyword */
4320static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
4321{
4322 return action_lookup(&tcp_check_keywords.list, kw);
4323}
4324
4325static void action_kw_tcp_check_build_list(struct buffer *chk)
4326{
4327 action_build_list(&tcp_check_keywords.list, chk);
4328}
4329
4330/* Create a tcp-check rule resulting from parsing a custom keyword. */
4331static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004332 struct list *rules, struct action_kw *kw,
4333 const char *file, int line, char **errmsg)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004334{
4335 struct tcpcheck_rule *chk = NULL;
4336 struct act_rule *actrule = NULL;
4337
4338 actrule = calloc(1, sizeof(*actrule));
4339 if (!actrule) {
4340 memprintf(errmsg, "out of memory");
4341 goto error;
4342 }
4343 actrule->kw = kw;
4344 actrule->from = ACT_F_TCP_CHK;
4345
4346 cur_arg++;
4347 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
4348 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
4349 goto error;
4350 }
4351
4352 chk = calloc(1, sizeof(*chk));
4353 if (!chk) {
4354 memprintf(errmsg, "out of memory");
4355 goto error;
4356 }
4357 chk->action = TCPCHK_ACT_ACTION_KW;
4358 chk->action_kw.rule = actrule;
4359 return chk;
4360
4361 error:
4362 free(actrule);
4363 return NULL;
4364}
4365
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004366static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Fauletb7d30092020-03-30 15:19:03 +02004367 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004368{
4369 struct tcpcheck_rule *chk = NULL;
Christopher Faulet5c288742020-03-31 08:15:58 +02004370 struct sockaddr_storage *sk = NULL;
Christopher Faulet98572322020-03-30 13:16:44 +02004371 char *comment = NULL, *sni = NULL, *alpn = NULL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004372 struct sample_expr *port_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004373 unsigned short conn_opts = 0;
4374 long port = 0;
Christopher Faulet98572322020-03-30 13:16:44 +02004375 int alpn_len = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004376
4377 list_for_each_entry(chk, rules, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004378 if (chk->action != TCPCHK_ACT_COMMENT && chk->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004379 break;
4380 }
4381 if (&chk->list != rules && chk->action != TCPCHK_ACT_CONNECT) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004382 memprintf(errmsg, "first step MUST also be a 'connect', "
4383 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
4384 "when there is a 'connect' step in the tcp-check ruleset");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004385 goto error;
4386 }
4387
4388 cur_arg++;
4389 while (*(args[cur_arg])) {
Christopher Fauletbb591a12020-04-01 16:52:17 +02004390 if (strcmp(args[cur_arg], "default") == 0)
4391 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
Christopher Faulet5c288742020-03-31 08:15:58 +02004392 else if (strcmp(args[cur_arg], "addr") == 0) {
4393 int port1, port2;
4394 struct protocol *proto;
4395
4396 if (!*(args[cur_arg+1])) {
4397 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
4398 goto error;
4399 }
4400
4401 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
4402 if (!sk) {
4403 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
4404 goto error;
4405 }
4406
4407 proto = protocol_by_family(sk->ss_family);
4408 if (!proto || !proto->connect) {
4409 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
4410 args[cur_arg]);
4411 goto error;
4412 }
4413
4414 if (port1 != port2) {
4415 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
4416 args[cur_arg], args[cur_arg+1]);
4417 goto error;
4418 }
4419
4420 cur_arg++;
4421 }
Christopher Faulet4dce5922020-03-30 13:54:42 +02004422 else if (strcmp(args[cur_arg], "port") == 0) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004423 const char *p, *end;
4424
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004425 if (!*(args[cur_arg+1])) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004426 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004427 goto error;
4428 }
4429 cur_arg++;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004430
4431 port = 0;
4432 release_sample_expr(port_expr);
4433 p = args[cur_arg]; end = p + strlen(p);
4434 port = read_uint(&p, end);
4435 if (p != end) {
4436 int idx = 0;
4437
4438 px->conf.args.ctx = ARGC_SRV;
4439 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4440 file, line, errmsg, &px->conf.args, NULL);
4441
4442 if (!port_expr) {
4443 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
4444 goto error;
4445 }
4446 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4447 memprintf(errmsg, "error detected while parsing port expression : "
4448 " fetch method '%s' extracts information from '%s', "
4449 "none of which is available here.\n",
4450 args[cur_arg], sample_src_names(port_expr->fetch->use));
4451 goto error;
4452 }
4453 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
4454 }
4455 else if (port > 65535 || port < 1) {
4456 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
4457 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004458 goto error;
4459 }
4460 }
4461 else if (strcmp(args[cur_arg], "comment") == 0) {
4462 if (!*(args[cur_arg+1])) {
4463 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4464 goto error;
4465 }
4466 cur_arg++;
4467 free(comment);
4468 comment = strdup(args[cur_arg]);
4469 if (!comment) {
4470 memprintf(errmsg, "out of memory");
4471 goto error;
4472 }
4473 }
4474 else if (strcmp(args[cur_arg], "send-proxy") == 0)
4475 conn_opts |= TCPCHK_OPT_SEND_PROXY;
Christopher Faulet085426a2020-03-30 13:07:02 +02004476 else if (strcmp(args[cur_arg], "via-socks4") == 0)
4477 conn_opts |= TCPCHK_OPT_SOCKS4;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004478 else if (strcmp(args[cur_arg], "linger") == 0)
4479 conn_opts |= TCPCHK_OPT_LINGER;
4480#ifdef USE_OPENSSL
4481 else if (strcmp(args[cur_arg], "ssl") == 0) {
4482 px->options |= PR_O_TCPCHK_SSL;
4483 conn_opts |= TCPCHK_OPT_SSL;
4484 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02004485 else if (strcmp(args[cur_arg], "sni") == 0) {
4486 if (!*(args[cur_arg+1])) {
4487 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4488 goto error;
4489 }
4490 cur_arg++;
4491 free(sni);
4492 sni = strdup(args[cur_arg]);
4493 if (!sni) {
4494 memprintf(errmsg, "out of memory");
4495 goto error;
4496 }
4497 }
Christopher Faulet98572322020-03-30 13:16:44 +02004498 else if (strcmp(args[cur_arg], "alpn") == 0) {
4499#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
4500 free(alpn);
4501 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
4502 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
4503 goto error;
4504 }
4505 cur_arg++;
4506#else
4507 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
4508 goto error;
4509#endif
4510 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004511#endif /* USE_OPENSSL */
4512
4513 else {
Christopher Faulet5c288742020-03-31 08:15:58 +02004514 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004515#ifdef USE_OPENSSL
Christopher Faulet98572322020-03-30 13:16:44 +02004516 ", 'ssl', 'sni', 'alpn'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004517#endif /* USE_OPENSSL */
Christopher Faulet4dce5922020-03-30 13:54:42 +02004518 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004519 args[cur_arg]);
4520 goto error;
4521 }
4522 cur_arg++;
4523 }
4524
4525 chk = calloc(1, sizeof(*chk));
4526 if (!chk) {
4527 memprintf(errmsg, "out of memory");
4528 goto error;
4529 }
Gaetan Rivet06d963a2020-02-21 18:49:05 +01004530 chk->action = TCPCHK_ACT_CONNECT;
4531 chk->comment = comment;
4532 chk->connect.port = port;
4533 chk->connect.options = conn_opts;
Christopher Faulet79b31d42020-03-30 13:00:05 +02004534 chk->connect.sni = sni;
Christopher Faulet98572322020-03-30 13:16:44 +02004535 chk->connect.alpn = alpn;
4536 chk->connect.alpn_len= alpn_len;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004537 chk->connect.port_expr= port_expr;
Christopher Faulet5c288742020-03-31 08:15:58 +02004538 if (sk)
4539 chk->connect.addr = *sk;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004540 return chk;
4541
4542 error:
Christopher Faulet98572322020-03-30 13:16:44 +02004543 free(alpn);
Christopher Faulet79b31d42020-03-30 13:00:05 +02004544 free(sni);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004545 free(comment);
Christopher Fauletb7d30092020-03-30 15:19:03 +02004546 release_sample_expr(port_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004547 return NULL;
4548}
4549
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004550static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004551 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004552{
4553 struct tcpcheck_rule *chk = NULL;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004554 char *comment = NULL, *data = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004555 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004556
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004557 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004558 if (!*(args[cur_arg+1])) {
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004559 memprintf(errmsg, "'%s' expects a %s as argument",
4560 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004561 goto error;
4562 }
4563
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004564 data = args[cur_arg+1];
4565
4566 cur_arg += 2;
4567 while (*(args[cur_arg])) {
4568 if (strcmp(args[cur_arg], "comment") == 0) {
4569 if (!*(args[cur_arg+1])) {
4570 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4571 goto error;
4572 }
4573 cur_arg++;
4574 free(comment);
4575 comment = strdup(args[cur_arg]);
4576 if (!comment) {
4577 memprintf(errmsg, "out of memory");
4578 goto error;
4579 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004580 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004581 else if (strcmp(args[cur_arg], "log-format") == 0) {
4582 if (type == TCPCHK_SEND_BINARY)
4583 type = TCPCHK_SEND_BINARY_LF;
4584 else if (type == TCPCHK_SEND_STRING)
4585 type = TCPCHK_SEND_STRING_LF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004586 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004587 else {
4588 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
4589 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004590 goto error;
4591 }
4592 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004593 }
4594
4595 chk = calloc(1, sizeof(*chk));
4596 if (!chk) {
4597 memprintf(errmsg, "out of memory");
4598 goto error;
4599 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004600 chk->action = TCPCHK_ACT_SEND;
4601 chk->comment = comment;
4602 chk->send.type = type;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004603
4604 switch (chk->send.type) {
4605 case TCPCHK_SEND_STRING:
4606 chk->send.data = ist2(strdup(data), strlen(data));
4607 if (!isttest(chk->send.data)) {
4608 memprintf(errmsg, "out of memory");
4609 goto error;
4610 }
4611 break;
4612 case TCPCHK_SEND_BINARY:
4613 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
4614 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
4615 goto error;
4616 }
4617 break;
4618 case TCPCHK_SEND_STRING_LF:
4619 case TCPCHK_SEND_BINARY_LF:
4620 LIST_INIT(&chk->send.fmt);
4621 px->conf.args.ctx = ARGC_SRV;
4622 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4623 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
4624 goto error;
4625 }
4626 break;
4627 case TCPCHK_SEND_UNDEF:
4628 goto error;
4629 }
4630
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004631 return chk;
4632
4633 error:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004634 free(chk);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004635 free(comment);
4636 return NULL;
4637}
4638
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004639static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
4640 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004641{
4642 struct tcpcheck_rule *chk = NULL;
4643 char *comment = NULL;
4644
4645 if (!*(args[cur_arg+1])) {
4646 memprintf(errmsg, "expects a string as argument");
4647 goto error;
4648 }
4649 cur_arg++;
4650 comment = strdup(args[cur_arg]);
4651 if (!comment) {
4652 memprintf(errmsg, "out of memory");
4653 goto error;
4654 }
4655
4656 chk = calloc(1, sizeof(*chk));
4657 if (!chk) {
4658 memprintf(errmsg, "out of memory");
4659 goto error;
4660 }
4661 chk->action = TCPCHK_ACT_COMMENT;
4662 chk->comment = comment;
4663 return chk;
4664
4665 error:
4666 free(comment);
4667 return NULL;
4668}
4669
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004670static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px, struct list *rules,
4671 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004672{
4673 struct tcpcheck_rule *prev_check, *chk = NULL;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004674 struct sample_expr *status_expr = NULL;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004675 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004676 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
Christopher Fauletec07e382020-04-07 14:56:26 +02004677 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004678 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
4679 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004680 long min_recv = -1;
4681 int inverse = 0, with_capture = 0;
4682
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004683 str = on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004684 if (!*(args[cur_arg+1])) {
4685 memprintf(errmsg, "expects at least a matching pattern as arguments");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004686 goto error;
4687 }
4688
4689 cur_arg++;
4690 while (*(args[cur_arg])) {
4691 int in_pattern = 0;
4692
4693 rescan:
4694 if (strcmp(args[cur_arg], "min-recv") == 0) {
4695 if (in_pattern) {
4696 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4697 goto error;
4698 }
4699 if (!*(args[cur_arg+1])) {
4700 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
4701 goto error;
4702 }
4703 /* Use an signed integer here because of chksize */
4704 cur_arg++;
4705 min_recv = atol(args[cur_arg]);
4706 if (min_recv < -1 || min_recv > INT_MAX) {
4707 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
4708 goto error;
4709 }
4710 }
4711 else if (*(args[cur_arg]) == '!') {
4712 in_pattern = 1;
4713 while (*(args[cur_arg]) == '!') {
4714 inverse = !inverse;
4715 args[cur_arg]++;
4716 }
4717 if (!*(args[cur_arg]))
4718 cur_arg++;
4719 goto rescan;
4720 }
4721 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "binary") == 0 ||
4722 strcmp(args[cur_arg], "rstring") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
4723 if (type != TCPCHK_EXPECT_UNDEF) {
4724 memprintf(errmsg, "only on pattern expected");
4725 goto error;
4726 }
4727 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING :
4728 ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY :
4729 ((*(args[cur_arg]+1) == 's') ? TCPCHK_EXPECT_REGEX : TCPCHK_EXPECT_REGEX_BINARY)));
4730
4731 if (!*(args[cur_arg+1])) {
4732 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4733 goto error;
4734 }
4735 cur_arg++;
4736 pattern = args[cur_arg];
4737 }
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004738 else if (strcmp(args[cur_arg], "custom") == 0) {
4739 if (in_pattern) {
4740 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4741 goto error;
4742 }
4743 if (type != TCPCHK_EXPECT_UNDEF) {
4744 memprintf(errmsg, "only on pattern expected");
4745 goto error;
4746 }
4747 type = TCPCHK_EXPECT_CUSTOM;
4748 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004749 else if (strcmp(args[cur_arg], "comment") == 0) {
4750 if (in_pattern) {
4751 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4752 goto error;
4753 }
4754 if (!*(args[cur_arg+1])) {
4755 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4756 goto error;
4757 }
4758 cur_arg++;
4759 free(comment);
4760 comment = strdup(args[cur_arg]);
4761 if (!comment) {
4762 memprintf(errmsg, "out of memory");
4763 goto error;
4764 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004765 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004766 else if (strcmp(args[cur_arg], "on-success") == 0) {
4767 if (in_pattern) {
4768 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4769 goto error;
4770 }
4771 if (!*(args[cur_arg+1])) {
4772 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4773 goto error;
4774 }
4775 cur_arg++;
4776 free(on_success_msg);
4777 on_success_msg = strdup(args[cur_arg]);
4778 if (!on_success_msg) {
4779 memprintf(errmsg, "out of memory");
4780 goto error;
4781 }
4782 }
4783 else if (strcmp(args[cur_arg], "on-error") == 0) {
4784 if (in_pattern) {
4785 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4786 goto error;
4787 }
4788 if (!*(args[cur_arg+1])) {
4789 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4790 goto error;
4791 }
4792 cur_arg++;
4793 free(on_error_msg);
4794 on_error_msg = strdup(args[cur_arg]);
4795 if (!on_error_msg) {
4796 memprintf(errmsg, "out of memory");
4797 goto error;
4798 }
Christopher Fauletec07e382020-04-07 14:56:26 +02004799 }
4800 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4801 if (in_pattern) {
4802 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4803 goto error;
4804 }
4805 if (!*(args[cur_arg+1])) {
4806 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4807 goto error;
4808 }
4809 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4810 ok_st = HCHK_STATUS_L7OKD;
4811 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4812 ok_st = HCHK_STATUS_L7OKCD;
4813 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4814 ok_st = HCHK_STATUS_L6OK;
4815 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4816 ok_st = HCHK_STATUS_L4OK;
4817 else {
4818 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4819 args[cur_arg], args[cur_arg+1]);
4820 goto error;
4821 }
4822 cur_arg++;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004823 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004824 else if (strcmp(args[cur_arg], "error-status") == 0) {
4825 if (in_pattern) {
4826 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4827 goto error;
4828 }
4829 if (!*(args[cur_arg+1])) {
4830 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4831 goto error;
4832 }
4833 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4834 err_st = HCHK_STATUS_L7RSP;
4835 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4836 err_st = HCHK_STATUS_L7STS;
4837 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4838 err_st = HCHK_STATUS_L6RSP;
4839 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4840 err_st = HCHK_STATUS_L4CON;
4841 else {
4842 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4843 args[cur_arg], args[cur_arg+1]);
4844 goto error;
4845 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004846 cur_arg++;
4847 }
4848 else if (strcmp(args[cur_arg], "status-code") == 0) {
4849 int idx = 0;
4850
4851 if (in_pattern) {
4852 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4853 goto error;
4854 }
4855 if (!*(args[cur_arg+1])) {
4856 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4857 goto error;
4858 }
4859
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004860 cur_arg++;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004861 release_sample_expr(status_expr);
4862 px->conf.args.ctx = ARGC_SRV;
4863 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4864 file, line, errmsg, &px->conf.args, NULL);
4865 if (!status_expr) {
4866 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4867 goto error;
4868 }
4869 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4870 memprintf(errmsg, "error detected while parsing status-code expression : "
4871 " fetch method '%s' extracts information from '%s', "
4872 "none of which is available here.\n",
4873 args[cur_arg], sample_src_names(status_expr->fetch->use));
4874 goto error;
4875 }
4876 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004877 }
4878 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4879 if (in_pattern) {
4880 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4881 goto error;
4882 }
4883 if (!*(args[cur_arg+1])) {
4884 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4885 goto error;
4886 }
4887 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4888 tout_st = HCHK_STATUS_L7TOUT;
4889 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4890 tout_st = HCHK_STATUS_L6TOUT;
4891 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4892 tout_st = HCHK_STATUS_L4TOUT;
4893 else {
4894 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4895 args[cur_arg], args[cur_arg+1]);
4896 goto error;
4897 }
4898 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004899 }
4900 else {
4901 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4902 " or comment but got '%s' as argument.", args[cur_arg]);
4903 goto error;
4904 }
4905
4906 cur_arg++;
4907 }
4908
4909 if (comment) {
4910 char *p = comment;
4911
4912 while (*p) {
4913 if (*p == '\\') {
4914 p++;
4915 if (!*p || !isdigit((unsigned char)*p) ||
4916 (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
4917 memprintf(errmsg, "invalid backreference in 'comment' argument");
4918 goto error;
4919 }
4920 with_capture = 1;
4921 }
4922 p++;
4923 }
4924 if (with_capture && !inverse)
4925 memprintf(errmsg, "using backreference in a positive expect comment is useless");
4926 }
4927
4928 chk = calloc(1, sizeof(*chk));
4929 if (!chk) {
4930 memprintf(errmsg, "out of memory");
4931 goto error;
4932 }
4933 chk->action = TCPCHK_ACT_EXPECT;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004934 LIST_INIT(&chk->expect.onerror_fmt);
4935 LIST_INIT(&chk->expect.onsuccess_fmt);
4936 chk->comment = comment; comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004937 chk->expect.type = type;
4938 chk->expect.min_recv = min_recv;
4939 chk->expect.inverse = inverse;
4940 chk->expect.with_capture = with_capture;
Christopher Fauletec07e382020-04-07 14:56:26 +02004941 chk->expect.ok_status = ok_st;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004942 chk->expect.err_status = err_st;
4943 chk->expect.tout_status = tout_st;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004944 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004945
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004946 if (on_success_msg) {
4947 px->conf.args.ctx = ARGC_SRV;
4948 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4949 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4950 goto error;
4951 }
4952 free(on_success_msg);
4953 on_success_msg = NULL;
4954 }
4955 if (on_error_msg) {
4956 px->conf.args.ctx = ARGC_SRV;
4957 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4958 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4959 goto error;
4960 }
4961 free(on_error_msg);
4962 on_error_msg = NULL;
4963 }
4964
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004965 switch (chk->expect.type) {
4966 case TCPCHK_EXPECT_STRING:
4967 chk->expect.string = strdup(pattern);
4968 chk->expect.length = strlen(pattern);
4969 if (!chk->expect.string) {
4970 memprintf(errmsg, "out of memory");
4971 goto error;
4972 }
4973 break;
4974 case TCPCHK_EXPECT_BINARY:
4975 if (parse_binary(pattern, &chk->expect.string, &chk->expect.length, errmsg) == 0) {
4976 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4977 goto error;
4978 }
4979 case TCPCHK_EXPECT_REGEX:
4980 case TCPCHK_EXPECT_REGEX_BINARY:
4981 chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
4982 if (!chk->expect.regex)
4983 goto error;
4984 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004985 case TCPCHK_EXPECT_CUSTOM:
4986 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4987 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004988 case TCPCHK_EXPECT_UNDEF:
4989 free(chk);
4990 memprintf(errmsg, "pattern not found");
4991 goto error;
4992 }
4993
4994 /* All tcp-check expect points back to the first inverse expect rule in
4995 * a chain of one or more expect rule, potentially itself.
4996 */
4997 chk->expect.head = chk;
4998 list_for_each_entry_rev(prev_check, rules, list) {
4999 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5000 if (prev_check->expect.inverse)
5001 chk->expect.head = prev_check;
5002 continue;
5003 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01005004 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005005 break;
5006 }
5007 return chk;
5008
5009 error:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005010 free_tcpcheck(chk, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005011 free(str);
5012 free(comment);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005013 free(on_success_msg);
5014 free(on_error_msg);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005015 release_sample_expr(status_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005016 return NULL;
5017}
5018
5019/* Parses the "tcp-check" proxy keyword */
5020static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5021 struct proxy *defpx, const char *file, int line,
5022 char **errmsg)
5023{
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005024 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005025 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005026 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005027
5028 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5029 ret = 1;
5030
5031 if (curpx == defpx) {
5032 memprintf(errmsg, "'%s' not allowed in 'defaults' section.", args[0]);
5033 goto error;
5034 }
5035
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005036 if (rules->flags & TCPCHK_RULES_DEF) {
5037 /* Only shared ruleset can be inherited from the default section */
5038 rules->flags = 0;
5039 rules->list = NULL;
5040 }
5041 if (rules->list && (rules->flags & TCPCHK_RULES_SHARED)) {
5042 memprintf(errmsg, "%s : A shared tcp-check ruleset already configured.", args[0]);
5043 goto error;
5044 }
5045
5046 if (!rules->list) {
5047 rules->list = calloc(1, sizeof(*rules->list));
5048 if (!rules->list) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005049 memprintf(errmsg, "%s : out of memory.", args[0]);
5050 goto error;
5051 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005052 LIST_INIT(rules->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005053 }
5054
Gaetan Rivet5301b012020-02-25 17:19:17 +01005055 index = 0;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005056 if (!LIST_ISEMPTY(rules->list)) {
5057 chk = LIST_PREV(rules->list, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005058 index = chk->index + 1;
5059 }
5060
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005061 cur_arg = 1;
5062 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005063 chk = parse_tcpcheck_connect(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005064 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005065 chk = parse_tcpcheck_send(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005066 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005067 chk = parse_tcpcheck_expect(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005068 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005069 chk = parse_tcpcheck_comment(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005070 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005071 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5072
5073 if (!kw) {
5074 action_kw_tcp_check_build_list(&trash);
5075 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5076 "%s%s. but got '%s'",
5077 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5078 goto error;
5079 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005080 chk = parse_tcpcheck_action(args, cur_arg, curpx, rules->list, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005081 }
5082
5083 if (!chk) {
5084 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5085 goto error;
5086 }
5087 ret = (*errmsg != NULL); /* Handle warning */
5088
5089 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005090 chk->index = index;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005091 LIST_ADDQ(rules->list, &chk->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005092 return ret;
5093
5094 error:
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005095 deinit_proxy_tcpcheck(curpx);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005096 return -1;
5097}
5098
Christopher Faulet33f05df2020-04-01 11:08:50 +02005099
5100static struct tcpcheck_ruleset *tcpcheck_ruleset_lookup(const char *name)
5101{
5102 struct tcpcheck_ruleset *rs;
5103
5104 list_for_each_entry(rs, &tcpchecks_list, list) {
5105 if (strcmp(rs->name, name) == 0)
5106 return rs;
5107 }
5108 return NULL;
5109}
5110
5111static struct tcpcheck_ruleset *tcpcheck_ruleset_create(const char *name)
5112{
5113 struct tcpcheck_ruleset *rs;
5114
5115 rs = calloc(1, sizeof(*rs));
5116 if (rs == NULL)
5117 return NULL;
5118
5119 rs->name = strdup(name);
5120 if (rs->name == NULL) {
5121 free(rs);
5122 return NULL;
5123 }
5124
5125 LIST_INIT(&rs->list);
5126 LIST_INIT(&rs->rules);
5127 LIST_ADDQ(&tcpchecks_list, &rs->list);
5128 return rs;
5129}
5130
5131static void tcpcheck_ruleset_release(struct tcpcheck_ruleset *rs)
5132{
5133 struct tcpcheck_rule *r, *rb;
5134 if (!rs)
5135 return;
5136
5137 LIST_DEL(&rs->list);
5138 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5139 LIST_DEL(&r->list);
5140 free_tcpcheck(r, 0);
5141 }
5142 free(rs->name);
5143 free(rs);
5144}
5145
5146
5147/* Parses the "option redis-check" proxy keyword */
5148int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5149 const char *file, int line)
5150{
5151 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5152 static char *redis_res = "+PONG\r\n";
5153
5154 struct tcpcheck_ruleset *rs = NULL;
5155 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5156 struct tcpcheck_rule *chk;
5157 char *errmsg = NULL;
5158 int err_code = 0;
5159
5160 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5161 err_code |= ERR_WARN;
5162
5163 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5164 goto out;
5165
5166 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5167 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5168 file, line);
5169 err_code |= ERR_ALERT | ERR_FATAL;
5170 goto out;
5171 }
5172
5173 curpx->options2 &= ~PR_O2_CHK_ANY;
5174 curpx->options2 |= PR_O2_TCPCHK_CHK;
5175
5176 free_tcpcheck_vars(&rules->preset_vars);
5177 rules->list = NULL;
5178 rules->flags = 0;
5179
5180 rs = tcpcheck_ruleset_lookup("*redis-check");
5181 if (rs)
5182 goto ruleset_found;
5183
5184 rs = tcpcheck_ruleset_create("*redis-check");
5185 if (rs == NULL) {
5186 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5187 goto error;
5188 }
5189
5190 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5191 1, curpx, &rs->rules, file, line, &errmsg);
5192 if (!chk) {
5193 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5194 goto error;
5195 }
5196 chk->index = 0;
5197 LIST_ADDQ(&rs->rules, &chk->list);
5198
5199 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5200 "error-status", "L7STS",
5201 "on-error", "%[check.payload(),cut_crlf]",
5202 "on-success", "Redis server is ok",
5203 ""},
5204 1, curpx, &rs->rules, file, line, &errmsg);
5205 if (!chk) {
5206 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5207 goto error;
5208 }
5209 chk->index = 1;
5210 LIST_ADDQ(&rs->rules, &chk->list);
5211
5212 LIST_ADDQ(&tcpchecks_list, &rs->list);
5213
5214 ruleset_found:
5215 rules->list = &rs->rules;
5216 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_REDIS_CHK);
5217
5218 out:
5219 free(errmsg);
5220 return err_code;
5221
5222 error:
5223 tcpcheck_ruleset_release(rs);
5224 err_code |= ERR_ALERT | ERR_FATAL;
5225 goto out;
5226}
5227
Christopher Faulet811f78c2020-04-01 11:10:27 +02005228
5229/* Parses the "option ssl-hello-chk" proxy keyword */
5230int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5231 const char *file, int line)
5232{
5233 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5234 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5235 *
5236 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5237 */
5238 static char sslv3_client_hello[] = {
5239 "16" /* ContentType : 0x16 = Hanshake */
5240 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5241 "0079" /* ContentLength : 0x79 bytes after this one */
5242 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5243 "000075" /* HandshakeLength : 0x75 bytes after this one */
5244 "0300" /* Hello Version : 0x0300 = v3 */
5245 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5246 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5247 "00" /* Session ID length : empty (no session ID) */
5248 "004E" /* Cipher Suite Length : 78 bytes after this one */
5249 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5250 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5251 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5252 "000D" "000E" "000F" "0010" /* various bit lengths, */
5253 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5254 "0015" "0016" "0017" "0018"
5255 "0019" "001A" "001B" "002F"
5256 "0030" "0031" "0032" "0033"
5257 "0034" "0035" "0036" "0037"
5258 "0038" "0039" "003A"
5259 "01" /* Compression Length : 0x01 = 1 byte for types */
5260 "00" /* Compression Type : 0x00 = NULL compression */
5261 };
5262
5263 struct tcpcheck_ruleset *rs = NULL;
5264 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5265 struct tcpcheck_rule *chk;
5266 char *errmsg = NULL;
5267 int err_code = 0;
5268
5269 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5270 err_code |= ERR_WARN;
5271
5272 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5273 goto out;
5274
5275 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5276 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5277 file, line);
5278 err_code |= ERR_ALERT | ERR_FATAL;
5279 goto out;
5280 }
5281
5282 curpx->options2 &= ~PR_O2_CHK_ANY;
5283 curpx->options2 |= PR_O2_TCPCHK_CHK;
5284
5285 free_tcpcheck_vars(&rules->preset_vars);
5286 rules->list = NULL;
5287 rules->flags = 0;
5288
5289 rs = tcpcheck_ruleset_lookup("*ssl-hello-check");
5290 if (rs)
5291 goto ruleset_found;
5292
5293 rs = tcpcheck_ruleset_create("*ssl-hello-check");
5294 if (rs == NULL) {
5295 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5296 goto error;
5297 }
5298
5299 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5300 1, curpx, &rs->rules, file, line, &errmsg);
5301 if (!chk) {
5302 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5303 goto error;
5304 }
5305 chk->index = 0;
5306 LIST_ADDQ(&rs->rules, &chk->list);
5307
5308 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005309 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005310 "error-status", "L6RSP", "tout-status", "L6TOUT",
5311 ""},
5312 1, curpx, &rs->rules, file, line, &errmsg);
5313 if (!chk) {
5314 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5315 goto error;
5316 }
5317 chk->index = 1;
5318 LIST_ADDQ(&rs->rules, &chk->list);
5319
5320 LIST_ADDQ(&tcpchecks_list, &rs->list);
5321
5322 ruleset_found:
5323 rules->list = &rs->rules;
5324 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SSL3_CHK);
5325
5326 out:
5327 free(errmsg);
5328 return err_code;
5329
5330 error:
5331 tcpcheck_ruleset_release(rs);
5332 err_code |= ERR_ALERT | ERR_FATAL;
5333 goto out;
5334}
5335
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005336/* Parses the "option smtpchk" proxy keyword */
5337int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5338 const char *file, int line)
5339{
5340 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5341
5342 struct tcpcheck_ruleset *rs = NULL;
5343 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5344 struct tcpcheck_rule *chk;
5345 struct tcpcheck_var *var = NULL;
5346 char *cmd = NULL, *errmsg = NULL;
5347 int err_code = 0;
5348
5349 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5350 err_code |= ERR_WARN;
5351
5352 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5353 goto out;
5354
5355 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5356 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5357 file, line);
5358 err_code |= ERR_ALERT | ERR_FATAL;
5359 goto out;
5360 }
5361
5362 curpx->options2 &= ~PR_O2_CHK_ANY;
5363 curpx->options2 |= PR_O2_TCPCHK_CHK;
5364
5365 free_tcpcheck_vars(&rules->preset_vars);
5366 rules->list = NULL;
5367 rules->flags = 0;
5368
5369 cur_arg += 2;
5370 if (*args[cur_arg] && *args[cur_arg+1] &&
5371 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5372 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5373 if (cmd)
5374 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
5375 }
5376 else {
5377 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
5378 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
5379 cmd = strdup("HELO localhost");
5380 }
5381
5382 var = tcpcheck_var_create("check.smtp_cmd");
5383 if (cmd == NULL || var == NULL) {
5384 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5385 goto error;
5386 }
5387 var->data.type = SMP_T_STR;
5388 var->data.u.str.area = cmd;
5389 var->data.u.str.data = strlen(cmd);
5390 LIST_INIT(&var->list);
5391 LIST_ADDQ(&rules->preset_vars, &var->list);
5392 cmd = NULL;
5393 var = NULL;
5394
5395 rs = tcpcheck_ruleset_lookup("*smtp-check");
5396 if (rs)
5397 goto ruleset_found;
5398
5399 rs = tcpcheck_ruleset_create("*smtp-check");
5400 if (rs == NULL) {
5401 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5402 goto error;
5403 }
5404
5405 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5406 1, curpx, &rs->rules, file, line, &errmsg);
5407 if (!chk) {
5408 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5409 goto error;
5410 }
5411 chk->index = 0;
5412 LIST_ADDQ(&rs->rules, &chk->list);
5413
5414 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
5415 "min-recv", "4",
5416 "error-status", "L7RSP",
5417 "on-error", "%[check.payload(),cut_crlf]",
5418 ""},
5419 1, curpx, &rs->rules, file, line, &errmsg);
5420 if (!chk) {
5421 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5422 goto error;
5423 }
5424 chk->index = 1;
5425 LIST_ADDQ(&rs->rules, &chk->list);
5426
5427 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
5428 "min-recv", "4",
5429 "error-status", "L7STS",
5430 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5431 "status-code", "check.payload(0,3)",
5432 ""},
5433 1, curpx, &rs->rules, file, line, &errmsg);
5434 if (!chk) {
5435 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5436 goto error;
5437 }
5438 chk->index = 2;
5439 LIST_ADDQ(&rs->rules, &chk->list);
5440
5441 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
5442 1, curpx, &rs->rules, file, line, &errmsg);
5443 if (!chk) {
5444 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5445 goto error;
5446 }
5447 chk->index = 3;
5448 LIST_ADDQ(&rs->rules, &chk->list);
5449
5450 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
5451 "min-recv", "4",
5452 "error-status", "L7STS",
5453 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5454 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5455 "status-code", "check.payload(0,3)",
5456 ""},
5457 1, curpx, &rs->rules, file, line, &errmsg);
5458 if (!chk) {
5459 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5460 goto error;
5461 }
5462 chk->index = 4;
5463 LIST_ADDQ(&rs->rules, &chk->list);
5464
5465 LIST_ADDQ(&tcpchecks_list, &rs->list);
5466
5467 ruleset_found:
5468 rules->list = &rs->rules;
5469 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SMTP_CHK);
5470
5471 out:
5472 free(errmsg);
5473 return err_code;
5474
5475 error:
5476 free(cmd);
5477 free(var);
5478 free_tcpcheck_vars(&rules->preset_vars);
5479 tcpcheck_ruleset_release(rs);
5480 err_code |= ERR_ALERT | ERR_FATAL;
5481 goto out;
5482}
Christopher Faulet811f78c2020-04-01 11:10:27 +02005483
Christopher Fauletce355072020-04-02 11:44:39 +02005484/* Parses the "option pgsql-check" proxy keyword */
5485int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5486 const char *file, int line)
5487{
5488 static char pgsql_req[] = {
5489 "%[var(check.plen),htonl,hex]" /* The packet length*/
5490 "00030000" /* the version 3.0 */
5491 "7573657200" /* "user" key */
5492 "%[var(check.username),hex]00" /* the username */
5493 "00"
5494 };
5495
5496 struct tcpcheck_ruleset *rs = NULL;
5497 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5498 struct tcpcheck_rule *chk;
5499 struct tcpcheck_var *var = NULL;
5500 char *user = NULL, *errmsg = NULL;
5501 size_t packetlen = 0;
5502 int err_code = 0;
5503
5504 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5505 err_code |= ERR_WARN;
5506
5507 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5508 goto out;
5509
5510 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5511 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5512 file, line);
5513 err_code |= ERR_ALERT | ERR_FATAL;
5514 goto out;
5515 }
5516
5517
5518 curpx->options2 &= ~PR_O2_CHK_ANY;
5519 curpx->options2 |= PR_O2_TCPCHK_CHK;
5520
5521 free_tcpcheck_vars(&rules->preset_vars);
5522 rules->list = NULL;
5523 rules->flags = 0;
5524
5525 cur_arg += 2;
5526 if (!*args[cur_arg] || !*args[cur_arg+1]) {
5527 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
5528 file, line, args[0], args[1]);
5529 goto error;
5530 }
5531 if (strcmp(args[cur_arg], "user") == 0) {
5532 packetlen = 15 + strlen(args[cur_arg+1]);
5533 user = strdup(args[cur_arg+1]);
5534
5535 var = tcpcheck_var_create("check.username");
5536 if (user == NULL || var == NULL) {
5537 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5538 goto error;
5539 }
5540 var->data.type = SMP_T_STR;
5541 var->data.u.str.area = user;
5542 var->data.u.str.data = strlen(user);
5543 LIST_INIT(&var->list);
5544 LIST_ADDQ(&rules->preset_vars, &var->list);
5545 user = NULL;
5546 var = NULL;
5547
5548 var = tcpcheck_var_create("check.plen");
5549 if (var == NULL) {
5550 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5551 goto error;
5552 }
5553 var->data.type = SMP_T_SINT;
5554 var->data.u.sint = packetlen;
5555 LIST_INIT(&var->list);
5556 LIST_ADDQ(&rules->preset_vars, &var->list);
5557 var = NULL;
5558 }
5559 else {
5560 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
5561 file, line, args[0], args[1]);
5562 goto error;
5563 }
5564
5565 rs = tcpcheck_ruleset_lookup("*pgsql-check");
5566 if (rs)
5567 goto ruleset_found;
5568
5569 rs = tcpcheck_ruleset_create("*pgsql-check");
5570 if (rs == NULL) {
5571 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5572 goto error;
5573 }
5574
5575 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5576 1, curpx, &rs->rules, file, line, &errmsg);
5577 if (!chk) {
5578 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5579 goto error;
5580 }
5581 chk->index = 0;
5582 LIST_ADDQ(&rs->rules, &chk->list);
5583
5584 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
5585 1, curpx, &rs->rules, file, line, &errmsg);
5586 if (!chk) {
5587 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5588 goto error;
5589 }
5590 chk->index = 1;
5591 LIST_ADDQ(&rs->rules, &chk->list);
5592
5593 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
5594 "min-recv", "5",
5595 "error-status", "L7RSP",
5596 "on-error", "%[check.payload(6,0)]",
5597 ""},
5598 1, curpx, &rs->rules, file, line, &errmsg);
5599 if (!chk) {
5600 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5601 goto error;
5602 }
5603 chk->index = 2;
5604 LIST_ADDQ(&rs->rules, &chk->list);
5605
5606 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
5607 "min-recv", "9",
5608 "error-status", "L7STS",
5609 "on-success", "PostgreSQL server is ok",
5610 "on-error", "PostgreSQL unknown error",
5611 ""},
5612 1, curpx, &rs->rules, file, line, &errmsg);
5613 if (!chk) {
5614 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5615 goto error;
5616 }
5617 chk->index = 3;
5618 LIST_ADDQ(&rs->rules, &chk->list);
5619
5620 LIST_ADDQ(&tcpchecks_list, &rs->list);
5621
5622 ruleset_found:
5623 rules->list = &rs->rules;
5624 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_PGSQL_CHK);
5625
5626 out:
5627 free(errmsg);
5628 return err_code;
5629
5630 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02005631 free(user);
5632 free(var);
5633 free_tcpcheck_vars(&rules->preset_vars);
5634 tcpcheck_ruleset_release(rs);
5635 err_code |= ERR_ALERT | ERR_FATAL;
5636 goto out;
5637}
5638
5639
5640/* Parses the "option mysql-check" proxy keyword */
5641int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5642 const char *file, int line)
5643{
5644 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
5645 * const char mysql40_client_auth_pkt[] = {
5646 * "\x0e\x00\x00" // packet length
5647 * "\x01" // packet number
5648 * "\x00\x00" // client capabilities
5649 * "\x00\x00\x01" // max packet
5650 * "haproxy\x00" // username (null terminated string)
5651 * "\x00" // filler (always 0x00)
5652 * "\x01\x00\x00" // packet length
5653 * "\x00" // packet number
5654 * "\x01" // COM_QUIT command
5655 * };
5656 */
5657 static char mysql40_rsname[] = "*mysql40-check";
5658 static char mysql40_req[] = {
5659 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5660 "0080" /* client capabilities */
5661 "000001" /* max packet */
5662 "%[var(check.username),hex]00" /* the username */
5663 "00" /* filler (always 0x00) */
5664 "010000" /* packet length*/
5665 "00" /* sequence ID */
5666 "01" /* COM_QUIT command */
5667 };
5668
5669 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
5670 * const char mysql41_client_auth_pkt[] = {
5671 * "\x0e\x00\x00\" // packet length
5672 * "\x01" // packet number
5673 * "\x00\x00\x00\x00" // client capabilities
5674 * "\x00\x00\x00\x01" // max packet
5675 * "\x21" // character set (UTF-8)
5676 * char[23] // All zeroes
5677 * "haproxy\x00" // username (null terminated string)
5678 * "\x00" // filler (always 0x00)
5679 * "\x01\x00\x00" // packet length
5680 * "\x00" // packet number
5681 * "\x01" // COM_QUIT command
5682 * };
5683 */
5684 static char mysql41_rsname[] = "*mysql41-check";
5685 static char mysql41_req[] = {
5686 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5687 "00820000" /* client capabilities */
5688 "00800001" /* max packet */
5689 "21" /* character set (UTF-8) */
5690 "000000000000000000000000" /* 23 bytes, al zeroes */
5691 "0000000000000000000000"
5692 "%[var(check.username),hex]00" /* the username */
5693 "00" /* filler (always 0x00) */
5694 "010000" /* packet length*/
5695 "00" /* sequence ID */
5696 "01" /* COM_QUIT command */
5697 };
5698
5699 struct tcpcheck_ruleset *rs = NULL;
5700 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5701 struct tcpcheck_rule *chk;
5702 struct tcpcheck_var *var = NULL;
5703 char *mysql_rsname = "*mysql-check";
5704 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
5705 int index = 0, err_code = 0;
5706
5707 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5708 err_code |= ERR_WARN;
5709
5710 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5711 goto out;
5712
5713 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5714 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5715 file, line);
5716 err_code |= ERR_ALERT | ERR_FATAL;
5717 goto out;
5718 }
5719
5720 curpx->options2 &= ~PR_O2_CHK_ANY;
5721 curpx->options2 |= PR_O2_TCPCHK_CHK;
5722
5723 free_tcpcheck_vars(&rules->preset_vars);
5724 rules->list = NULL;
5725 rules->flags = 0;
5726
5727 cur_arg += 2;
5728 if (*args[cur_arg]) {
5729 char *user;
5730 int packetlen, userlen;
5731
5732 if (strcmp(args[cur_arg], "user") != 0) {
5733 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
5734 file, line, args[0], args[1], args[cur_arg]);
5735 goto error;
5736 }
5737
5738 if (*(args[cur_arg+1]) == 0) {
5739 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
5740 file, line, args[0], args[1], args[cur_arg]);
5741 goto error;
5742 }
5743
5744 hdr = calloc(4, sizeof(*hdr));
5745 user = strdup(args[cur_arg+1]);
5746 userlen = strlen(args[cur_arg+1]);
5747
5748 if (hdr == NULL || user == NULL) {
5749 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5750 goto error;
5751 }
5752
5753 if (*args[cur_arg+2]) {
5754 if (strcmp(args[cur_arg+2], "post-41") != 0) {
5755 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
5756 file, line, args[cur_arg], args[cur_arg+2]);
5757 goto error;
5758 }
5759 packetlen = userlen + 7 + 27;
5760 mysql_req = mysql41_req;
5761 mysql_rsname = mysql41_rsname;
5762 }
5763 else {
5764 packetlen = userlen + 7;
5765 mysql_req = mysql40_req;
5766 mysql_rsname = mysql40_rsname;
5767 }
5768
5769 hdr[0] = (unsigned char)(packetlen & 0xff);
5770 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
5771 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
5772 hdr[3] = 1;
5773
5774 var = tcpcheck_var_create("check.header");
5775 if (var == NULL) {
5776 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5777 goto error;
5778 }
5779 var->data.type = SMP_T_STR;
5780 var->data.u.str.area = hdr;
5781 var->data.u.str.data = 4;
5782 LIST_INIT(&var->list);
5783 LIST_ADDQ(&rules->preset_vars, &var->list);
5784 hdr = NULL;
5785 var = NULL;
5786
5787 var = tcpcheck_var_create("check.username");
5788 if (var == NULL) {
5789 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5790 goto error;
5791 }
5792 var->data.type = SMP_T_STR;
5793 var->data.u.str.area = user;
5794 var->data.u.str.data = strlen(user);
5795 LIST_INIT(&var->list);
5796 LIST_ADDQ(&rules->preset_vars, &var->list);
5797 user = NULL;
5798 var = NULL;
5799 }
5800
5801 rs = tcpcheck_ruleset_lookup(mysql_rsname);
5802 if (rs)
5803 goto ruleset_found;
5804
5805 rs = tcpcheck_ruleset_create(mysql_rsname);
5806 if (rs == NULL) {
5807 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5808 goto error;
5809 }
5810
5811 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5812 1, curpx, &rs->rules, file, line, &errmsg);
5813 if (!chk) {
5814 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5815 goto error;
5816 }
5817 chk->index = index++;
5818 LIST_ADDQ(&rs->rules, &chk->list);
5819
5820 if (mysql_req) {
5821 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
5822 1, curpx, &rs->rules, file, line, &errmsg);
5823 if (!chk) {
5824 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5825 goto error;
5826 }
5827 chk->index = index++;
5828 LIST_ADDQ(&rs->rules, &chk->list);
5829 }
5830
5831 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5832 1, curpx, &rs->rules, file, line, &errmsg);
5833 if (!chk) {
5834 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5835 goto error;
5836 }
5837 chk->expect.custom = tcpcheck_mysql_expect_iniths;
5838 chk->index = index++;
5839 LIST_ADDQ(&rs->rules, &chk->list);
5840
5841 if (mysql_req) {
5842 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5843 1, curpx, &rs->rules, file, line, &errmsg);
5844 if (!chk) {
5845 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5846 goto error;
5847 }
5848 chk->expect.custom = tcpcheck_mysql_expect_ok;
5849 chk->index = index++;
5850 LIST_ADDQ(&rs->rules, &chk->list);
5851 }
5852
5853 LIST_ADDQ(&tcpchecks_list, &rs->list);
5854
5855 ruleset_found:
5856 rules->list = &rs->rules;
5857 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_MYSQL_CHK);
5858
5859 out:
5860 free(errmsg);
5861 return err_code;
5862
5863 error:
5864 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02005865 free(user);
5866 free(var);
5867 free_tcpcheck_vars(&rules->preset_vars);
5868 tcpcheck_ruleset_release(rs);
5869 err_code |= ERR_ALERT | ERR_FATAL;
5870 goto out;
5871}
5872
Christopher Faulet1997eca2020-04-03 23:13:50 +02005873int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5874 const char *file, int line)
5875{
5876 static char *ldap_req = "300C020101600702010304008000";
5877
5878 struct tcpcheck_ruleset *rs = NULL;
5879 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5880 struct tcpcheck_rule *chk;
5881 char *errmsg = NULL;
5882 int err_code = 0;
5883
5884 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5885 err_code |= ERR_WARN;
5886
5887 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5888 goto out;
5889
5890 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5891 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5892 file, line);
5893 err_code |= ERR_ALERT | ERR_FATAL;
5894 goto out;
5895 }
5896
5897 curpx->options2 &= ~PR_O2_CHK_ANY;
5898 curpx->options2 |= PR_O2_TCPCHK_CHK;
5899
5900 free_tcpcheck_vars(&rules->preset_vars);
5901 rules->list = NULL;
5902 rules->flags = 0;
5903
5904 rs = tcpcheck_ruleset_lookup("*ldap-check");
5905 if (rs)
5906 goto ruleset_found;
5907
5908 rs = tcpcheck_ruleset_create("*ldap-check");
5909 if (rs == NULL) {
5910 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5911 goto error;
5912 }
5913
5914 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
5915 1, curpx, &rs->rules, file, line, &errmsg);
5916 if (!chk) {
5917 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5918 goto error;
5919 }
5920 chk->index = 0;
5921 LIST_ADDQ(&rs->rules, &chk->list);
5922
5923 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
5924 "min-recv", "14",
5925 "on-error", "Not LDAPv3 protocol",
5926 ""},
5927 1, curpx, &rs->rules, file, line, &errmsg);
5928 if (!chk) {
5929 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5930 goto error;
5931 }
5932 chk->index = 1;
5933 LIST_ADDQ(&rs->rules, &chk->list);
5934
5935 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5936 1, curpx, &rs->rules, file, line, &errmsg);
5937 if (!chk) {
5938 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5939 goto error;
5940 }
5941 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
5942 chk->index = 2;
5943 LIST_ADDQ(&rs->rules, &chk->list);
5944
5945 LIST_ADDQ(&tcpchecks_list, &rs->list);
5946
5947 ruleset_found:
5948 rules->list = &rs->rules;
5949 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_LDAP_CHK);
5950
5951 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02005952 free(errmsg);
5953 return err_code;
5954
5955 error:
5956 tcpcheck_ruleset_release(rs);
5957 err_code |= ERR_ALERT | ERR_FATAL;
5958 goto out;
5959}
5960
5961int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5962 const char *file, int line)
5963{
5964 struct tcpcheck_ruleset *rs = NULL;
5965 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5966 struct tcpcheck_rule *chk;
5967 char *spop_req = NULL;
5968 char *errmsg = NULL;
5969 int spop_len = 0, err_code = 0;
5970
5971 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5972 err_code |= ERR_WARN;
5973
5974 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5975 goto out;
5976
5977 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5978 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5979 file, line);
5980 err_code |= ERR_ALERT | ERR_FATAL;
5981 goto out;
5982 }
5983
5984 curpx->options2 &= ~PR_O2_CHK_ANY;
5985 curpx->options2 |= PR_O2_TCPCHK_CHK;
5986
5987 free_tcpcheck_vars(&rules->preset_vars);
5988 rules->list = NULL;
5989 rules->flags = 0;
5990
5991
5992 rs = tcpcheck_ruleset_lookup("*spop-check");
5993 if (rs)
5994 goto ruleset_found;
5995
5996 rs = tcpcheck_ruleset_create("*spop-check");
5997 if (rs == NULL) {
5998 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5999 goto error;
6000 }
6001
6002 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6003 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6004 goto error;
6005 }
6006 chunk_reset(&trash);
6007 dump_binary(&trash, spop_req, spop_len);
6008 trash.area[trash.data] = '\0';
6009
6010 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6011 1, curpx, &rs->rules, file, line, &errmsg);
6012 if (!chk) {
6013 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6014 goto error;
6015 }
6016 chk->index = 0;
6017 LIST_ADDQ(&rs->rules, &chk->list);
6018
6019 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
6020 1, curpx, &rs->rules, file, line, &errmsg);
6021 if (!chk) {
6022 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6023 goto error;
6024 }
6025 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6026 chk->index = 1;
6027 LIST_ADDQ(&rs->rules, &chk->list);
6028
6029 LIST_ADDQ(&tcpchecks_list, &rs->list);
6030
6031 ruleset_found:
6032 rules->list = &rs->rules;
6033 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SPOP_CHK);
6034
6035 out:
6036 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006037 free(errmsg);
6038 return err_code;
6039
6040 error:
6041 tcpcheck_ruleset_release(rs);
6042 err_code |= ERR_ALERT | ERR_FATAL;
6043 goto out;
6044}
Christopher Fauletce355072020-04-02 11:44:39 +02006045
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006046
Christopher Fauletce8111e2020-04-06 15:04:11 +02006047/* Parse the "addr" server keyword */
6048static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6049 char **errmsg)
6050{
6051 struct sockaddr_storage *sk;
6052 struct protocol *proto;
6053 int port1, port2, err_code = 0;
6054
6055
6056 if (!*args[*cur_arg+1]) {
6057 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6058 goto error;
6059 }
6060
6061 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6062 if (!sk) {
6063 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6064 goto error;
6065 }
6066
6067 proto = protocol_by_family(sk->ss_family);
6068 if (!proto || !proto->connect) {
6069 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6070 args[*cur_arg], args[*cur_arg+1]);
6071 goto error;
6072 }
6073
6074 if (port1 != port2) {
6075 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6076 args[*cur_arg], args[*cur_arg+1]);
6077 goto error;
6078 }
6079
6080 srv->check.addr = srv->agent.addr = *sk;
6081 srv->flags |= SRV_F_CHECKADDR;
6082 srv->flags |= SRV_F_AGENTADDR;
6083
6084 out:
6085 return err_code;
6086
6087 error:
6088 err_code |= ERR_ALERT | ERR_FATAL;
6089 goto out;
6090}
6091
6092
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006093/* Parse the "agent-addr" server keyword */
6094static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6095 char **errmsg)
6096{
6097 int err_code = 0;
6098
6099 if (!*(args[*cur_arg+1])) {
6100 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6101 goto error;
6102 }
6103 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6104 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6105 goto error;
6106 }
6107
6108 out:
6109 return err_code;
6110
6111 error:
6112 err_code |= ERR_ALERT | ERR_FATAL;
6113 goto out;
6114}
6115
6116/* Parse the "agent-check" server keyword */
6117static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6118 char **errmsg)
6119{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006120 struct tcpcheck_ruleset *rs = NULL;
6121 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6122 struct tcpcheck_rule *chk;
6123 int err_code = 0;
6124
6125 if (srv->do_agent)
6126 goto out;
6127
6128 if (!rules) {
6129 rules = calloc(1, sizeof(*rules));
6130 if (!rules) {
6131 memprintf(errmsg, "out of memory.");
6132 goto error;
6133 }
6134 LIST_INIT(&rules->preset_vars);
6135 srv->agent.tcpcheck_rules = rules;
6136 }
6137 rules->list = NULL;
6138 rules->flags = 0;
6139
6140 rs = tcpcheck_ruleset_lookup("*agent-check");
6141 if (rs)
6142 goto ruleset_found;
6143
6144 rs = tcpcheck_ruleset_create("*agent-check");
6145 if (rs == NULL) {
6146 memprintf(errmsg, "out of memory.");
6147 goto error;
6148 }
6149
6150 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6151 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6152 if (!chk) {
6153 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6154 goto error;
6155 }
6156 chk->index = 0;
6157 LIST_ADDQ(&rs->rules, &chk->list);
6158
6159 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6160 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6161 if (!chk) {
6162 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6163 goto error;
6164 }
6165 chk->expect.custom = tcpcheck_agent_expect_reply;
6166 chk->index = 1;
6167 LIST_ADDQ(&rs->rules, &chk->list);
6168
6169 LIST_ADDQ(&tcpchecks_list, &rs->list);
6170
6171 ruleset_found:
6172 rules->list = &rs->rules;
6173 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_AGENT_CHK);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006174 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006175
6176 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006177 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006178
6179 error:
6180 deinit_srv_agent_check(srv);
6181 tcpcheck_ruleset_release(rs);
6182 err_code |= ERR_ALERT | ERR_FATAL;
6183 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006184}
6185
6186/* Parse the "agent-inter" server keyword */
6187static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6188 char **errmsg)
6189{
6190 const char *err = NULL;
6191 unsigned int delay;
6192 int err_code = 0;
6193
6194 if (!*(args[*cur_arg+1])) {
6195 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6196 goto error;
6197 }
6198
6199 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6200 if (err == PARSE_TIME_OVER) {
6201 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6202 args[*cur_arg+1], args[*cur_arg], srv->id);
6203 goto error;
6204 }
6205 else if (err == PARSE_TIME_UNDER) {
6206 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6207 args[*cur_arg+1], args[*cur_arg], srv->id);
6208 goto error;
6209 }
6210 else if (err) {
6211 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6212 *err, srv->id);
6213 goto error;
6214 }
6215 if (delay <= 0) {
6216 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6217 delay, args[*cur_arg], srv->id);
6218 goto error;
6219 }
6220 srv->agent.inter = delay;
6221
6222 out:
6223 return err_code;
6224
6225 error:
6226 err_code |= ERR_ALERT | ERR_FATAL;
6227 goto out;
6228}
6229
6230/* Parse the "agent-port" server keyword */
6231static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6232 char **errmsg)
6233{
6234 int err_code = 0;
6235
6236 if (!*(args[*cur_arg+1])) {
6237 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6238 goto error;
6239 }
6240
6241 global.maxsock++;
6242 srv->agent.port = atol(args[*cur_arg+1]);
6243
6244 out:
6245 return err_code;
6246
6247 error:
6248 err_code |= ERR_ALERT | ERR_FATAL;
6249 goto out;
6250}
6251
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006252int set_srv_agent_send(struct server *srv, const char *send)
6253{
6254 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6255 struct tcpcheck_var *var = NULL;
6256 char *str;
6257
6258 str = strdup(send);
6259 var = tcpcheck_var_create("check.agent_string");
6260 if (str == NULL || var == NULL)
6261 goto error;
6262
6263 free_tcpcheck_vars(&rules->preset_vars);
6264
6265 var->data.type = SMP_T_STR;
6266 var->data.u.str.area = str;
6267 var->data.u.str.data = strlen(str);
6268 LIST_INIT(&var->list);
6269 LIST_ADDQ(&rules->preset_vars, &var->list);
6270
6271 return 1;
6272
6273 error:
6274 free(str);
6275 free(var);
6276 return 0;
6277}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006278
6279/* Parse the "agent-send" server keyword */
6280static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6281 char **errmsg)
6282{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006283 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006284 int err_code = 0;
6285
6286 if (!*(args[*cur_arg+1])) {
6287 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
6288 goto error;
6289 }
6290
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006291 if (!rules) {
6292 rules = calloc(1, sizeof(*rules));
6293 if (!rules) {
6294 memprintf(errmsg, "out of memory.");
6295 goto error;
6296 }
6297 LIST_INIT(&rules->preset_vars);
6298 srv->agent.tcpcheck_rules = rules;
6299 }
6300
6301 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006302 memprintf(errmsg, "out of memory.");
6303 goto error;
6304 }
6305
6306 out:
6307 return err_code;
6308
6309 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006310 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006311 err_code |= ERR_ALERT | ERR_FATAL;
6312 goto out;
6313}
6314
6315/* Parse the "no-agent-send" server keyword */
6316static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6317 char **errmsg)
6318{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006319 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006320 return 0;
6321}
6322
Christopher Fauletce8111e2020-04-06 15:04:11 +02006323/* Parse the "check" server keyword */
6324static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6325 char **errmsg)
6326{
6327 srv->do_check = 1;
6328 return 0;
6329}
6330
6331/* Parse the "check-send-proxy" server keyword */
6332static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6333 char **errmsg)
6334{
6335 srv->check.send_proxy = 1;
6336 return 0;
6337}
6338
6339/* Parse the "check-via-socks4" server keyword */
6340static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6341 char **errmsg)
6342{
6343 srv->check.via_socks4 = 1;
6344 return 0;
6345}
6346
6347/* Parse the "no-check" server keyword */
6348static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6349 char **errmsg)
6350{
6351 deinit_srv_check(srv);
6352 return 0;
6353}
6354
6355/* Parse the "no-check-send-proxy" server keyword */
6356static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6357 char **errmsg)
6358{
6359 srv->check.send_proxy = 0;
6360 return 0;
6361}
6362
6363/* Parse the "rise" server keyword */
6364static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6365 char **errmsg)
6366{
6367 int err_code = 0;
6368
6369 if (!*args[*cur_arg + 1]) {
6370 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6371 goto error;
6372 }
6373
6374 srv->check.rise = atol(args[*cur_arg+1]);
6375 if (srv->check.rise <= 0) {
6376 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6377 goto error;
6378 }
6379
6380 if (srv->check.health)
6381 srv->check.health = srv->check.rise;
6382
6383 out:
6384 return err_code;
6385
6386 error:
6387 deinit_srv_agent_check(srv);
6388 err_code |= ERR_ALERT | ERR_FATAL;
6389 goto out;
6390 return 0;
6391}
6392
6393/* Parse the "fall" server keyword */
6394static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6395 char **errmsg)
6396{
6397 int err_code = 0;
6398
6399 if (!*args[*cur_arg + 1]) {
6400 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6401 goto error;
6402 }
6403
6404 srv->check.fall = atol(args[*cur_arg+1]);
6405 if (srv->check.fall <= 0) {
6406 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6407 goto error;
6408 }
6409
6410 out:
6411 return err_code;
6412
6413 error:
6414 deinit_srv_agent_check(srv);
6415 err_code |= ERR_ALERT | ERR_FATAL;
6416 goto out;
6417 return 0;
6418}
6419
6420/* Parse the "inter" server keyword */
6421static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6422 char **errmsg)
6423{
6424 const char *err = NULL;
6425 unsigned int delay;
6426 int err_code = 0;
6427
6428 if (!*(args[*cur_arg+1])) {
6429 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6430 goto error;
6431 }
6432
6433 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6434 if (err == PARSE_TIME_OVER) {
6435 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6436 args[*cur_arg+1], args[*cur_arg], srv->id);
6437 goto error;
6438 }
6439 else if (err == PARSE_TIME_UNDER) {
6440 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6441 args[*cur_arg+1], args[*cur_arg], srv->id);
6442 goto error;
6443 }
6444 else if (err) {
6445 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6446 *err, srv->id);
6447 goto error;
6448 }
6449 if (delay <= 0) {
6450 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6451 delay, args[*cur_arg], srv->id);
6452 goto error;
6453 }
6454 srv->check.inter = delay;
6455
6456 out:
6457 return err_code;
6458
6459 error:
6460 err_code |= ERR_ALERT | ERR_FATAL;
6461 goto out;
6462}
6463
6464
6465/* Parse the "fastinter" server keyword */
6466static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6467 char **errmsg)
6468{
6469 const char *err = NULL;
6470 unsigned int delay;
6471 int err_code = 0;
6472
6473 if (!*(args[*cur_arg+1])) {
6474 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6475 goto error;
6476 }
6477
6478 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6479 if (err == PARSE_TIME_OVER) {
6480 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6481 args[*cur_arg+1], args[*cur_arg], srv->id);
6482 goto error;
6483 }
6484 else if (err == PARSE_TIME_UNDER) {
6485 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6486 args[*cur_arg+1], args[*cur_arg], srv->id);
6487 goto error;
6488 }
6489 else if (err) {
6490 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6491 *err, srv->id);
6492 goto error;
6493 }
6494 if (delay <= 0) {
6495 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6496 delay, args[*cur_arg], srv->id);
6497 goto error;
6498 }
6499 srv->check.fastinter = delay;
6500
6501 out:
6502 return err_code;
6503
6504 error:
6505 err_code |= ERR_ALERT | ERR_FATAL;
6506 goto out;
6507}
6508
6509
6510/* Parse the "downinter" server keyword */
6511static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6512 char **errmsg)
6513{
6514 const char *err = NULL;
6515 unsigned int delay;
6516 int err_code = 0;
6517
6518 if (!*(args[*cur_arg+1])) {
6519 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6520 goto error;
6521 }
6522
6523 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6524 if (err == PARSE_TIME_OVER) {
6525 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6526 args[*cur_arg+1], args[*cur_arg], srv->id);
6527 goto error;
6528 }
6529 else if (err == PARSE_TIME_UNDER) {
6530 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6531 args[*cur_arg+1], args[*cur_arg], srv->id);
6532 goto error;
6533 }
6534 else if (err) {
6535 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6536 *err, srv->id);
6537 goto error;
6538 }
6539 if (delay <= 0) {
6540 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6541 delay, args[*cur_arg], srv->id);
6542 goto error;
6543 }
6544 srv->check.downinter = delay;
6545
6546 out:
6547 return err_code;
6548
6549 error:
6550 err_code |= ERR_ALERT | ERR_FATAL;
6551 goto out;
6552}
6553
6554/* Parse the "port" server keyword */
6555static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6556 char **errmsg)
6557{
6558 int err_code = 0;
6559
6560 if (!*(args[*cur_arg+1])) {
6561 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6562 goto error;
6563 }
6564
6565 global.maxsock++;
6566 srv->check.port = atol(args[*cur_arg+1]);
6567 srv->flags |= SRV_F_CHECKPORT;
6568
6569 out:
6570 return err_code;
6571
6572 error:
6573 err_code |= ERR_ALERT | ERR_FATAL;
6574 goto out;
6575}
6576
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006577static struct cfg_kw_list cfg_kws = {ILH, {
6578 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
6579 { 0, NULL, NULL },
6580}};
6581
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006582static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02006583 { "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 +02006584 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
6585 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
6586 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
6587 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
6588 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006589 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
6590 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
6591 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006592 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006593 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
6594 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
6595 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
6596 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
6597 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
6598 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
6599 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
6600 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006601 { NULL, NULL, 0 },
6602}};
6603
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006604INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006605INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006606
Willy Tarreaubd741542010-03-16 18:46:54 +01006607/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02006608 * Local variables:
6609 * c-indent-level: 8
6610 * c-basic-offset: 8
6611 * End:
6612 */