blob: aacaf612c1ec3e837d12363762a0b1d28d7c129c [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 */
2455 set_server_check_status(check, HCHK_STATUS_L7OKD, b_peek(&check->bi, 5));
2456
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
2539 set_server_check_status(check, HCHK_STATUS_L7OKD, "Success");
2540
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
2581 set_server_check_status(check, HCHK_STATUS_L7OKD, "SPOA server is ok");
2582
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;
3107 struct tcpcheck_expect *expect = &check->current_step->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);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003222 struct buffer *msg = NULL;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003223 int must_read = 1, last_read = 0;
3224 int ret, retcode = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003225
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003226 /* here, we know that the check is complete or that it failed */
3227 if (check->result != CHK_RES_UNKNOWN)
3228 goto out_end_tcpcheck;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003229
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003230 /* 1- check for connection error, if any */
3231 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3232 goto out_end_tcpcheck;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003233
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003234 /* 2- check if we are waiting for the connection establishment. It only
3235 * happens during TCPCHK_ACT_CONNECT. */
3236 if (conn && (conn->flags & CO_FL_WAIT_XPRT))
3237 goto out;
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003238
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003239 /* 3- check for pending outgoing data. It only happens during TCPCHK_ACT_SEND. */
3240 if (conn && b_data(&check->bo)) {
3241 ret = conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
3242 if (ret <= 0) {
3243 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3244 goto out_end_tcpcheck;
3245 goto out;
3246 }
3247 if (b_data(&check->bo)) {
3248 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3249 goto out;
3250 }
3251 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003252
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003253 /* Now evaluate the tcp-check rules */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003254
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003255 /* If check->current_step is defined, we are in resume condition. For
3256 * TCPCHK_ACT_CONNECT and TCPCHK_ACT_SEND rules, we must go to the next
3257 * rule before resuming the evaluation. For TCPCHK_ACT_EXPECT, we
3258 * re-evaluate the current rule. Others cannot yield.
3259 */
3260 if (check->current_step) {
3261 if (check->current_step->action == TCPCHK_ACT_CONNECT ||
3262 check->current_step->action == TCPCHK_ACT_SEND)
3263 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
3264 else
3265 rule = check->current_step;
3266 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003267 else {
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003268 struct tcpcheck_var *var;
3269
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003270 /* First evaluation, create a session */
Gaetan Rivet13a50432020-02-21 18:13:44 +01003271 check->sess = session_new(&checks_fe, NULL, (check->server ? &check->server->obj_type : NULL));
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003272 if (!check->sess) {
3273 chunk_printf(&trash, "TCPCHK error allocating check session");
3274 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3275 goto out_end_tcpcheck;
3276 }
Gaetan Rivet13a50432020-02-21 18:13:44 +01003277 vars_init(&check->vars, SCOPE_CHECK);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003278 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003279
3280 /* Preset tcp-check variables */
3281 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
3282 struct sample smp;
3283
3284 memset(&smp, 0, sizeof(smp));
3285 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
3286 smp.data = var->data;
3287 vars_set_by_name_ifexist(var->name.ptr, var->name.len, &smp);
3288 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003289 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003290
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003291 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003292 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003293
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003294 switch (rule->action) {
3295 case TCPCHK_ACT_CONNECT:
3296 check->current_step = rule;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003297
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003298 /* close but not release yet previous connection */
3299 if (check->cs) {
3300 cs_close(check->cs);
3301 retcode = -1; /* do not reuse the fd in the caller! */
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003302 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003303 eval_ret = tcpcheck_eval_connect(check, rule);
3304 must_read = 1; last_read = 0;
3305 break;
3306 case TCPCHK_ACT_SEND:
3307 check->current_step = rule;
3308 eval_ret = tcpcheck_eval_send(check, rule);
3309 must_read = 1;
3310 break;
3311 case TCPCHK_ACT_EXPECT:
3312 check->current_step = rule;
3313 if (must_read) {
3314 if (check->proxy->timeout.check)
3315 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003316
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003317 /* If we already subscribed, then we tried to received and
3318 * failed, so there's no point trying again.
3319 */
3320 if (check->wait_list.events & SUB_RETRY_RECV)
3321 goto out;
3322 if (conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0) <= 0) {
3323 if (conn->flags & (CO_FL_ERROR|CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
3324 last_read = 1;
3325 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
3326 /* Report network errors only if we got no other data. Otherwise
3327 * we'll let the upper layers decide whether the response is OK
3328 * or not. It is very common that an RST sent by the server is
3329 * reported as an error just after the last data chunk.
3330 */
3331 goto out_end_tcpcheck;
3332 }
3333 }
3334 else {
3335 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
3336 goto out;
3337 }
3338 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003339
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003340 /* buffer full, don't wait for more data */
3341 if (b_full(&check->bi))
3342 last_read = 1;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003343
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003344 /* Check that response body is not empty... */
3345 if (!b_data(&check->bi)) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003346 if (!last_read)
3347 goto out;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003348
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003349 /* empty response */
3350 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
3351 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003352 if (rule->comment)
3353 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003354 set_server_check_status(check, rule->expect.err_status, trash.area);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003355 ret = -1;
3356 goto out_end_tcpcheck;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003357 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003358 must_read = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003359 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003360
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003361 eval_ret = tcpcheck_eval_expect(check, rule, last_read);
3362 if (eval_ret == TCPCHK_EVAL_WAIT) {
3363 check->current_step = rule->expect.head;
3364 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003365 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003366 break;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003367 case TCPCHK_ACT_ACTION_KW:
3368 /* Don't update the current step */
3369 eval_ret = tcpcheck_eval_action_kw(check, rule);
3370 break;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003371 default:
3372 /* Otherwise, just go to the next one and don't update
3373 * the current step
3374 */
3375 eval_ret = TCPCHK_EVAL_CONTINUE;
3376 break;
3377 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003378
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003379 switch (eval_ret) {
3380 case TCPCHK_EVAL_CONTINUE:
3381 break;
3382 case TCPCHK_EVAL_WAIT:
3383 goto out;
3384 case TCPCHK_EVAL_STOP:
3385 goto out_end_tcpcheck;
Baptiste Assmann248f1172018-03-01 21:49:01 +01003386 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003387 }
3388
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003389 /* All rules was evaluated */
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003390 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet206368d2020-04-03 14:51:06 +02003391 msg = alloc_trash_chunk();
3392 if (msg)
3393 tcpcheck_onsuccess_message(msg, check, check->current_step, ist(NULL));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003394 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003395 set_server_check_status(check, HCHK_STATUS_L7OKD, (msg ? b_head(msg) : "(tcp-check)"));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003396 free_trash_chunk(msg);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003397
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003398 out_end_tcpcheck:
Willy Tarreauef91c932019-07-23 14:37:47 +02003399 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003400 chk_report_conn_err(check, errno, 0);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003401
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003402 /* cleanup before leaving */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003403 check->current_step = NULL;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003404 if (check->sess != NULL) {
Gaetan Rivet13a50432020-02-21 18:13:44 +01003405 vars_prune(&check->vars, check->sess, NULL);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003406 session_free(check->sess);
3407 check->sess = NULL;
3408 }
3409 out:
3410 return retcode;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003411}
3412
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003413static const char *init_check(struct check *check, int type)
Simon Hormanb1900d52015-01-30 11:22:54 +09003414{
3415 check->type = type;
3416
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003417 b_reset(&check->bi); check->bi.size = global.tune.chksize;
3418 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Simon Hormanb1900d52015-01-30 11:22:54 +09003419
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003420 check->bi.area = calloc(check->bi.size, sizeof(char));
3421 check->bo.area = calloc(check->bo.size, sizeof(char));
3422
3423 if (!check->bi.area || !check->bo.area)
Simon Hormanb1900d52015-01-30 11:22:54 +09003424 return "out of memory while allocating check buffer";
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003425
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003426 check->wait_list.tasklet = tasklet_new();
3427 if (!check->wait_list.tasklet)
Ilya Shipitsind4259502020-04-08 01:07:56 +05003428 return "out of memory while allocating check tasklet";
Willy Tarreau4f6516d2018-12-19 13:59:17 +01003429 check->wait_list.events = 0;
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003430 check->wait_list.tasklet->process = event_srv_chk_io;
3431 check->wait_list.tasklet->context = check;
Simon Hormanb1900d52015-01-30 11:22:54 +09003432 return NULL;
3433}
3434
Simon Hormanbfb5d332015-01-30 11:22:55 +09003435void free_check(struct check *check)
3436{
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003437 task_destroy(check->task);
3438 if (check->wait_list.tasklet)
3439 tasklet_free(check->wait_list.tasklet);
3440
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003441 free(check->bi.area);
3442 free(check->bo.area);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003443 if (check->cs) {
3444 free(check->cs->conn);
3445 check->cs->conn = NULL;
3446 cs_free(check->cs);
3447 check->cs = NULL;
3448 }
Simon Hormanbfb5d332015-01-30 11:22:55 +09003449}
3450
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003451static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
3452{
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003453 struct logformat_node *lf, *lfb;
3454
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003455 if (!rule)
3456 return;
3457
3458 free(rule->comment);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003459 switch (rule->action) {
3460 case TCPCHK_ACT_SEND:
3461 switch (rule->send.type) {
3462 case TCPCHK_SEND_STRING:
3463 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003464 free(rule->send.data.ptr);
3465 break;
3466 case TCPCHK_SEND_STRING_LF:
3467 case TCPCHK_SEND_BINARY_LF:
3468 list_for_each_entry_safe(lf, lfb, &rule->send.fmt, list) {
3469 LIST_DEL(&lf->list);
3470 release_sample_expr(lf->expr);
3471 free(lf->arg);
3472 free(lf);
3473 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003474 break;
3475 case TCPCHK_SEND_UNDEF:
3476 break;
3477 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003478 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003479 case TCPCHK_ACT_EXPECT:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003480 list_for_each_entry_safe(lf, lfb, &rule->expect.onerror_fmt, list) {
3481 LIST_DEL(&lf->list);
3482 release_sample_expr(lf->expr);
3483 free(lf->arg);
3484 free(lf);
3485 }
3486 list_for_each_entry_safe(lf, lfb, &rule->expect.onsuccess_fmt, list) {
3487 LIST_DEL(&lf->list);
3488 release_sample_expr(lf->expr);
3489 free(lf->arg);
3490 free(lf);
3491 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003492 release_sample_expr(rule->expect.status_expr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003493 switch (rule->expect.type) {
3494 case TCPCHK_EXPECT_STRING:
3495 case TCPCHK_EXPECT_BINARY:
3496 free(rule->expect.string);
3497 break;
3498 case TCPCHK_EXPECT_REGEX:
3499 case TCPCHK_EXPECT_REGEX_BINARY:
3500 regex_free(rule->expect.regex);
3501 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003502 case TCPCHK_EXPECT_CUSTOM:
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003503 case TCPCHK_EXPECT_UNDEF:
3504 break;
3505 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003506 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003507 case TCPCHK_ACT_CONNECT:
Christopher Faulet79b31d42020-03-30 13:00:05 +02003508 free(rule->connect.sni);
Christopher Faulet98572322020-03-30 13:16:44 +02003509 free(rule->connect.alpn);
Christopher Fauletb7d30092020-03-30 15:19:03 +02003510 release_sample_expr(rule->connect.port_expr);
Christopher Faulet79b31d42020-03-30 13:00:05 +02003511 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003512 case TCPCHK_ACT_COMMENT:
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003513 break;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01003514 case TCPCHK_ACT_ACTION_KW:
3515 free(rule->action_kw.rule);
3516 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003517 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003518
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003519 if (in_pool)
3520 pool_free(pool_head_tcpcheck_rule, rule);
3521 else
3522 free(rule);
3523}
3524
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003525
Christopher Fauletce355072020-04-02 11:44:39 +02003526static struct tcpcheck_var *tcpcheck_var_create(const char *name)
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003527{
3528 struct tcpcheck_var *var = NULL;
3529
3530 var = calloc(1, sizeof(*var));
3531 if (var == NULL)
3532 return NULL;
3533
3534 var->name = ist2(strdup(name), strlen(name));
3535 if (var->name.ptr == NULL) {
3536 free(var);
3537 return NULL;
3538 }
3539
3540 LIST_INIT(&var->list);
3541 return var;
3542}
3543
3544static void tcpcheck_var_release(struct tcpcheck_var *var)
3545{
3546 if (!var)
3547 return;
3548
3549 free(var->name.ptr);
3550 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
3551 free(var->data.u.str.area);
3552 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
3553 free(var->data.u.meth.str.area);
3554 free(var);
3555}
3556
3557int dup_tcpcheck_vars(struct list *dst, struct list *src)
3558{
3559 struct tcpcheck_var *var, *new = NULL;
3560
3561 list_for_each_entry(var, src, list) {
3562 new = tcpcheck_var_create(var->name.ptr);
3563 if (!new)
3564 goto error;
3565 new->data.type = var->data.type;
3566 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
3567 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3568 goto error;
3569 if (var->data.type == SMP_T_STR)
3570 new->data.u.str.area[new->data.u.str.data] = 0;
3571 }
3572 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
3573 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3574 goto error;
3575 new->data.u.str.area[new->data.u.str.data] = 0;
3576 new->data.u.meth.meth = var->data.u.meth.meth;
3577 }
3578 else
3579 new->data.u = var->data.u;
3580 LIST_ADDQ(dst, &new->list);
3581 }
3582 return 1;
3583
3584 error:
3585 free(new);
3586 return 0;
3587}
3588
3589static void free_tcpcheck_vars(struct list *vars)
3590{
3591 struct tcpcheck_var *var, *back;
3592
3593 list_for_each_entry_safe(var, back, vars, list) {
3594 LIST_DEL(&var->list);
3595 tcpcheck_var_release(var);
3596 }
3597}
3598
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003599void email_alert_free(struct email_alert *alert)
3600{
3601 struct tcpcheck_rule *rule, *back;
3602
3603 if (!alert)
3604 return;
3605
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003606 if (alert->rules.list) {
3607 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
3608 LIST_DEL(&rule->list);
3609 free_tcpcheck(rule, 1);
3610 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003611 free_tcpcheck_vars(&alert->rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003612 free(alert->rules.list);
3613 alert->rules.list = NULL;
Christopher Fauletde1a75b2017-10-23 15:38:19 +02003614 }
Willy Tarreaubafbe012017-11-24 17:34:44 +01003615 pool_free(pool_head_email_alert, alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003616}
3617
Olivier Houchard9f6af332018-05-25 14:04:04 +02003618static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003619{
Olivier Houchard9f6af332018-05-25 14:04:04 +02003620 struct check *check = context;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003621 struct email_alertq *q;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003622 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003623
3624 q = container_of(check, typeof(*q), check);
3625
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003626 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003627 while (1) {
3628 if (!(check->state & CHK_ST_ENABLED)) {
3629 if (LIST_ISEMPTY(&q->email_alerts)) {
3630 /* All alerts processed, queue the task */
3631 t->expire = TICK_ETERNITY;
3632 task_queue(t);
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003633 goto end;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003634 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003635
3636 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003637 LIST_DEL(&alert->list);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003638 t->expire = now_ms;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003639 check->tcpcheck_rules = &alert->rules;
Olivier Houchard0923fa42019-01-11 18:43:04 +01003640 check->status = HCHK_STATUS_INI;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003641 check->state |= CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003642 }
3643
Olivier Houchard9f6af332018-05-25 14:04:04 +02003644 process_chk(t, context, state);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003645 if (check->state & CHK_ST_INPROGRESS)
3646 break;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003647
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003648 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003649 email_alert_free(alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003650 check->tcpcheck_rules = NULL;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003651 check->server = NULL;
3652 check->state &= ~CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003653 }
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003654 end:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003655 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003656 return t;
3657}
3658
Christopher Faulet0108bb32017-10-20 21:34:32 +02003659/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
3660 *
3661 * The function returns 1 in success case, otherwise, it returns 0 and err is
3662 * filled.
3663 */
3664int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003665{
Christopher Faulet0108bb32017-10-20 21:34:32 +02003666 struct mailer *mailer;
3667 struct email_alertq *queues;
3668 const char *err_str;
3669 int i = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003670
Christopher Faulet0108bb32017-10-20 21:34:32 +02003671 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
3672 memprintf(err, "out of memory while allocating mailer alerts queues");
mildis5ab01cb2018-10-02 16:46:34 +02003673 goto fail_no_queue;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003674 }
3675
Christopher Faulet0108bb32017-10-20 21:34:32 +02003676 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
3677 struct email_alertq *q = &queues[i];
3678 struct check *check = &q->check;
3679 struct task *t;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003680
3681 LIST_INIT(&q->email_alerts);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003682 HA_SPIN_INIT(&q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003683 check->inter = mls->timeout.mail;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003684 check->rise = DEF_AGENT_RISETIME;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01003685 check->proxy = p;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003686 check->fall = DEF_AGENT_FALLTIME;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003687 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
3688 memprintf(err, "%s", err_str);
3689 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003690 }
3691
3692 check->xprt = mailer->xprt;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003693 check->addr = mailer->addr;
Christopher Fauletb797ae12018-03-27 15:35:35 +02003694 check->port = get_host_port(&mailer->addr);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003695
Emeric Brunc60def82017-09-27 14:59:38 +02003696 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003697 memprintf(err, "out of memory while allocating mailer alerts task");
3698 goto error;
3699 }
3700
3701 check->task = t;
3702 t->process = process_email_alert;
3703 t->context = check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003704
Christopher Faulet0108bb32017-10-20 21:34:32 +02003705 /* check this in one ms */
3706 t->expire = TICK_ETERNITY;
3707 check->start = now;
3708 task_queue(t);
3709 }
3710
3711 mls->users++;
3712 free(p->email_alert.mailers.name);
3713 p->email_alert.mailers.m = mls;
3714 p->email_alert.queues = queues;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003715 return 0;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003716
3717 error:
3718 for (i = 0; i < mls->count; i++) {
3719 struct email_alertq *q = &queues[i];
3720 struct check *check = &q->check;
3721
Christopher Faulet0108bb32017-10-20 21:34:32 +02003722 free_check(check);
3723 }
3724 free(queues);
mildis5ab01cb2018-10-02 16:46:34 +02003725 fail_no_queue:
Christopher Faulet0108bb32017-10-20 21:34:32 +02003726 return 1;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003727}
3728
3729
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003730static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003731{
Gaetan Rivet4038b942020-02-26 16:19:40 +01003732 struct tcpcheck_rule *tcpcheck, *prev_check;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003733 struct tcpcheck_expect *expect;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003734
Willy Tarreaubafbe012017-11-24 17:34:44 +01003735 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003736 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003737 memset(tcpcheck, 0, sizeof(*tcpcheck));
Gaetan Rivetb616add2020-02-07 15:37:17 +01003738 tcpcheck->action = TCPCHK_ACT_EXPECT;
3739
3740 expect = &tcpcheck->expect;
3741 expect->type = TCPCHK_EXPECT_STRING;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003742 LIST_INIT(&expect->onerror_fmt);
3743 LIST_INIT(&expect->onsuccess_fmt);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003744 expect->err_status = HCHK_STATUS_L7RSP;
3745 expect->tout_status = HCHK_STATUS_L7TOUT;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003746 expect->string = strdup(str);
3747 if (!expect->string) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003748 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003749 return 0;
3750 }
Gaetan Rivetb616add2020-02-07 15:37:17 +01003751 expect->length = strlen(expect->string);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003752
Gaetan Rivet4038b942020-02-26 16:19:40 +01003753 /* All tcp-check expect points back to the first inverse expect rule
3754 * in a chain of one or more expect rule, potentially itself.
3755 */
Gaetan Rivetb616add2020-02-07 15:37:17 +01003756 tcpcheck->expect.head = tcpcheck;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003757 list_for_each_entry_rev(prev_check, rules->list, list) {
Gaetan Rivet4038b942020-02-26 16:19:40 +01003758 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Gaetan Rivetb616add2020-02-07 15:37:17 +01003759 if (prev_check->expect.inverse)
3760 tcpcheck->expect.head = prev_check;
Gaetan Rivet4038b942020-02-26 16:19:40 +01003761 continue;
3762 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003763 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet4038b942020-02-26 16:19:40 +01003764 break;
3765 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003766 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003767 return 1;
3768}
3769
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003770static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003771{
3772 struct tcpcheck_rule *tcpcheck;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003773 struct tcpcheck_send *send;
Willy Tarreau64345aa2016-08-10 19:29:09 +02003774 const char *in;
3775 char *dst;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003776 int i;
3777
Willy Tarreaubafbe012017-11-24 17:34:44 +01003778 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003779 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003780 memset(tcpcheck, 0, sizeof(*tcpcheck));
3781 tcpcheck->action = TCPCHK_ACT_SEND;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003782
3783 send = &tcpcheck->send;
3784 send->type = TCPCHK_SEND_STRING;
3785
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003786 for (i = 0; strs[i]; i++)
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003787 send->data.len += strlen(strs[i]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003788
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003789 send->data.ptr = malloc(send->data.len + 1);
3790 if (!isttest(send->data)) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003791 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003792 return 0;
3793 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003794
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003795 dst = send->data.ptr;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003796 for (i = 0; strs[i]; i++)
Willy Tarreau64345aa2016-08-10 19:29:09 +02003797 for (in = strs[i]; (*dst = *in++); dst++);
3798 *dst = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003799
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003800 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003801 return 1;
3802}
3803
Christopher Faulet0108bb32017-10-20 21:34:32 +02003804static int enqueue_one_email_alert(struct proxy *p, struct server *s,
3805 struct email_alertq *q, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003806{
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003807 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003808 struct tcpcheck_rule *tcpcheck;
3809 struct check *check = &q->check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003810
Willy Tarreaubafbe012017-11-24 17:34:44 +01003811 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003812 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003813 LIST_INIT(&alert->list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003814 alert->rules.flags = 0;
3815 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
3816 if (!alert->rules.list)
3817 goto error;
3818 LIST_INIT(alert->rules.list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003819 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
Christopher Faulet0108bb32017-10-20 21:34:32 +02003820 alert->srv = s;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003821
Willy Tarreaubafbe012017-11-24 17:34:44 +01003822 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003823 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003824 memset(tcpcheck, 0, sizeof(*tcpcheck));
3825 tcpcheck->action = TCPCHK_ACT_CONNECT;
3826 tcpcheck->comment = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003827
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003828 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003829
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003830 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003831 goto error;
3832
3833 {
3834 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003835 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003836 goto error;
3837 }
3838
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003839 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003840 goto error;
3841
3842 {
3843 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003844 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003845 goto error;
3846 }
3847
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003848 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003849 goto error;
3850
3851 {
3852 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003853 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003854 goto error;
3855 }
3856
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003857 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003858 goto error;
3859
3860 {
3861 const char * const strs[2] = { "DATA\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003862 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003863 goto error;
3864 }
3865
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003866 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003867 goto error;
3868
3869 {
3870 struct tm tm;
3871 char datestr[48];
3872 const char * const strs[18] = {
Pieter Baauw5e0964e2016-02-13 16:27:35 +01003873 "From: ", p->email_alert.from, "\r\n",
3874 "To: ", p->email_alert.to, "\r\n",
3875 "Date: ", datestr, "\r\n",
3876 "Subject: [HAproxy Alert] ", msg, "\r\n",
3877 "\r\n",
3878 msg, "\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003879 "\r\n",
Pieter Baauwed35c372015-07-22 19:51:54 +02003880 ".\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003881 NULL
3882 };
3883
3884 get_localtime(date.tv_sec, &tm);
3885
3886 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
3887 goto error;
3888 }
3889
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003890 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003891 goto error;
3892 }
3893
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003894 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003895 goto error;
3896
3897 {
3898 const char * const strs[2] = { "QUIT\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003899 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003900 goto error;
3901 }
3902
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003903 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003904 goto error;
3905
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003906 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003907 task_wakeup(check->task, TASK_WOKEN_MSG);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003908 LIST_ADDQ(&q->email_alerts, &alert->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003909 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003910 return 1;
3911
3912error:
3913 email_alert_free(alert);
3914 return 0;
3915}
3916
Christopher Faulet0108bb32017-10-20 21:34:32 +02003917static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003918{
3919 int i;
3920 struct mailer *mailer;
3921
3922 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
3923 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003924 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003925 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003926 return;
3927 }
3928 }
3929
3930 return;
3931}
3932
3933/*
3934 * Send email alert if configured.
3935 */
Simon Horman64e34162015-02-06 11:11:57 +09003936void send_email_alert(struct server *s, int level, const char *format, ...)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003937{
3938 va_list argp;
3939 char buf[1024];
3940 int len;
3941 struct proxy *p = s->proxy;
3942
Christopher Faulet0108bb32017-10-20 21:34:32 +02003943 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003944 return;
3945
3946 va_start(argp, format);
3947 len = vsnprintf(buf, sizeof(buf), format, argp);
3948 va_end(argp);
3949
Thierry FOURNIER62c8a212017-02-09 12:19:27 +01003950 if (len < 0 || len >= sizeof(buf)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003951 ha_alert("Email alert [%s] could not format message\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003952 return;
3953 }
3954
Christopher Faulet0108bb32017-10-20 21:34:32 +02003955 enqueue_email_alert(p, s, buf);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003956}
3957
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003958/*
3959 * Return value:
3960 * the port to be used for the health check
3961 * 0 in case no port could be found for the check
3962 */
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003963static int srv_check_healthcheck_port(struct check *chk)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003964{
3965 int i = 0;
3966 struct server *srv = NULL;
3967
3968 srv = chk->server;
3969
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003970 /* by default, we use the health check port ocnfigured */
3971 if (chk->port > 0)
3972 return chk->port;
3973
3974 /* try to get the port from check_core.addr if check.port not set */
3975 i = get_host_port(&chk->addr);
3976 if (i > 0)
3977 return i;
3978
3979 /* try to get the port from server address */
3980 /* prevent MAPPORTS from working at this point, since checks could
3981 * not be performed in such case (MAPPORTS impose a relative ports
3982 * based on live traffic)
3983 */
3984 if (srv->flags & SRV_F_MAPPORTS)
3985 return 0;
Willy Tarreau04276f32017-01-06 17:41:29 +01003986
3987 i = srv->svc_port; /* by default */
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003988 if (i > 0)
3989 return i;
3990
3991 return 0;
3992}
3993
Willy Tarreau172f5ce2018-11-26 11:21:50 +01003994REGISTER_POST_CHECK(start_checks);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003995
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003996static int check_proxy_tcpcheck(struct proxy *px)
3997{
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003998 struct tcpcheck_rule *chk, *back;
3999 char *comment = NULL;
4000 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004001 int ret = 0;
4002
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004003 if ((px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004004 goto out;
4005
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004006 if (!px->tcpcheck_rules.list) {
4007 px->tcpcheck_rules.list = calloc(1, sizeof(*px->tcpcheck_rules.list));
4008 if (!px->tcpcheck_rules.list) {
4009 ha_alert("config : proxy '%s': out of memory.\n", px->id);
4010 ret |= ERR_ALERT | ERR_FATAL;
4011 goto out;
4012 }
4013 LIST_INIT(px->tcpcheck_rules.list);
4014 }
4015
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004016 /* If there is no connect rule preceeding all send / expect rules, an
4017 * implicit one is inserted before all others
4018 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004019 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004020 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4021 chk = calloc(1, sizeof(*chk));
4022 if (!chk) {
4023 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4024 "(out of memory).\n", px->id);
4025 ret |= ERR_ALERT | ERR_FATAL;
4026 goto out;
4027 }
4028 chk->action = TCPCHK_ACT_CONNECT;
Christopher Fauletbb591a12020-04-01 16:52:17 +02004029 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004030 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004031 }
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004032
4033 /* Now remove comment rules */
4034 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4035 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4036 free(comment);
4037 comment = NULL;
4038 }
4039
4040 prev_action = chk->action;
4041 switch (chk->action) {
4042 case TCPCHK_ACT_COMMENT:
4043 free(comment);
4044 comment = chk->comment;
4045 LIST_DEL(&chk->list);
4046 free(chk);
4047 break;
4048 case TCPCHK_ACT_CONNECT:
4049 if (!chk->comment && comment)
4050 chk->comment = strdup(comment);
4051 /* fall though */
4052 case TCPCHK_ACT_ACTION_KW:
4053 free(comment);
4054 comment = NULL;
4055 break;
4056 case TCPCHK_ACT_SEND:
4057 case TCPCHK_ACT_EXPECT:
4058 if (!chk->comment && comment)
4059 chk->comment = strdup(comment);
4060 break;
4061 }
4062 }
4063 free(comment);
4064 comment = NULL;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004065
4066 out:
4067 return ret;
4068}
4069
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004070static int init_srv_check(struct server *srv)
4071{
4072 const char *err;
4073 struct tcpcheck_rule *r;
4074 int ret = 0;
4075
4076 if (!srv->do_check)
4077 goto out;
4078
4079
4080 /* If neither a port nor an addr was specified and no check transport
4081 * layer is forced, then the transport layer used by the checks is the
4082 * same as for the production traffic. Otherwise we use raw_sock by
4083 * default, unless one is specified.
4084 */
4085 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4086 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4087 srv->check.use_ssl = srv->use_ssl;
4088 srv->check.xprt = srv->xprt;
4089 }
4090 else if (srv->check.use_ssl == 1)
4091 srv->check.xprt = xprt_get(XPRT_SSL);
4092
4093 srv->check.send_proxy |= (srv->pp_opts);
4094 }
4095
4096 /* validate <srv> server health-check settings */
4097
4098 /* We need at least a service port, a check port or the first tcp-check
4099 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4100 */
4101 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4102 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4103 goto init;
4104
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004105 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004106 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4107 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4108 ret |= ERR_ALERT | ERR_ABORT;
4109 goto out;
4110 }
4111
4112 /* search the first action (connect / send / expect) in the list */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004113 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
Christopher Faulet5c288742020-03-31 08:15:58 +02004114 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004115 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4116 "nor tcp_check rule 'connect' with port information.\n",
4117 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4118 ret |= ERR_ALERT | ERR_ABORT;
4119 goto out;
4120 }
4121
4122 /* scan the tcp-check ruleset to ensure a port has been configured */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004123 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
Christopher Faulet5c288742020-03-31 08:15:58 +02004124 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004125 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4126 "and a tcp_check rule 'connect' with no port information.\n",
4127 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4128 ret |= ERR_ALERT | ERR_ABORT;
4129 goto out;
4130 }
4131 }
4132
4133 init:
4134 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4135 if (err) {
4136 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4137 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4138 ret |= ERR_ALERT | ERR_ABORT;
4139 goto out;
4140 }
4141 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4142 global.maxsock++;
4143
4144 out:
4145 return ret;
4146}
4147
4148static int init_srv_agent_check(struct server *srv)
4149{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004150 struct tcpcheck_rule *chk;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004151 const char *err;
4152 int ret = 0;
4153
4154 if (!srv->do_agent)
4155 goto out;
4156
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004157 /* If there is no connect rule preceeding all send / expect rules, an
4158 * implicit one is inserted before all others.
4159 */
4160 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4161 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4162 chk = calloc(1, sizeof(*chk));
4163 if (!chk) {
4164 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4165 " to agent-check for server '%s' (out of memory).\n",
4166 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4167 ret |= ERR_ALERT | ERR_FATAL;
4168 goto out;
4169 }
4170 chk->action = TCPCHK_ACT_CONNECT;
4171 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4172 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
4173 }
4174
4175
4176 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004177 if (err) {
4178 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4179 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4180 ret |= ERR_ALERT | ERR_ABORT;
4181 goto out;
4182 }
4183
4184 if (!srv->agent.inter)
4185 srv->agent.inter = srv->check.inter;
4186
4187 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4188 global.maxsock++;
4189
4190 out:
4191 return ret;
4192}
4193
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004194void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004195{
4196 struct tcpcheck_rule *chk, *back;
4197
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004198 if (!px->tcpcheck_rules.list || (px->tcpcheck_rules.flags & TCPCHK_RULES_SHARED))
4199 goto end;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004200
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004201 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004202 LIST_DEL(&chk->list);
4203 free_tcpcheck(chk, 0);
4204 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02004205 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004206 free(px->tcpcheck_rules.list);
4207
4208 end:
4209 px->tcpcheck_rules.flags = 0;
4210 px->tcpcheck_rules.list = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004211}
4212
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004213static void deinit_srv_check(struct server *srv)
4214{
Christopher Fauletce8111e2020-04-06 15:04:11 +02004215 if (srv->check.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004216 free_check(&srv->check);
Christopher Fauletce8111e2020-04-06 15:04:11 +02004217 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
4218 srv->do_check = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004219}
4220
4221
4222static void deinit_srv_agent_check(struct server *srv)
4223{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004224 if (srv->agent.tcpcheck_rules) {
4225 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
4226 free(srv->agent.tcpcheck_rules);
4227 srv->agent.tcpcheck_rules = NULL;
4228 }
4229
4230 if (srv->agent.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004231 free_check(&srv->agent);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004232
4233 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
4234 srv->do_agent = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004235}
4236
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004237static void deinit_tcpchecks()
4238{
4239 struct tcpcheck_ruleset *rs, *rsb;
4240 struct tcpcheck_rule *r, *rb;
4241
4242 list_for_each_entry_safe(rs, rsb, &tcpchecks_list, list) {
4243 LIST_DEL(&rs->list);
4244 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4245 LIST_DEL(&r->list);
4246 free_tcpcheck(r, 0);
4247 }
4248 free(rs->name);
4249 free(rs);
4250 }
4251}
4252
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004253
4254REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004255REGISTER_POST_SERVER_CHECK(init_srv_check);
4256REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
4257
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004258REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004259REGISTER_SERVER_DEINIT(deinit_srv_check);
4260REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004261REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004262
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004263/* extracts check payload at a fixed position and length */
4264static int
4265smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
4266{
4267 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
4268 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
4269 struct server *srv = (smp->sess ? objt_server(smp->sess->origin) : NULL);
4270 struct buffer *buf;
4271
4272 if (!srv || !srv->do_check)
4273 return 0;
4274
4275 buf = &srv->check.bi;
4276 if (buf_offset > b_data(buf))
4277 goto no_match;
4278 if (buf_offset + buf_size > b_data(buf))
4279 buf_size = 0;
4280
4281 /* init chunk as read only */
4282 smp->data.type = SMP_T_STR;
4283 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
4284 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
4285
4286 return 1;
4287
4288 no_match:
4289 smp->flags = 0;
4290 return 0;
4291}
4292
4293static struct sample_fetch_kw_list smp_kws = {ILH, {
4294 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
4295 { /* END */ },
4296}};
4297
4298INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
4299
4300
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004301struct action_kw_list tcp_check_keywords = {
4302 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
4303};
4304
4305/* Return the struct action_kw associated to a keyword */
4306static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
4307{
4308 return action_lookup(&tcp_check_keywords.list, kw);
4309}
4310
4311static void action_kw_tcp_check_build_list(struct buffer *chk)
4312{
4313 action_build_list(&tcp_check_keywords.list, chk);
4314}
4315
4316/* Create a tcp-check rule resulting from parsing a custom keyword. */
4317static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004318 struct list *rules, struct action_kw *kw,
4319 const char *file, int line, char **errmsg)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004320{
4321 struct tcpcheck_rule *chk = NULL;
4322 struct act_rule *actrule = NULL;
4323
4324 actrule = calloc(1, sizeof(*actrule));
4325 if (!actrule) {
4326 memprintf(errmsg, "out of memory");
4327 goto error;
4328 }
4329 actrule->kw = kw;
4330 actrule->from = ACT_F_TCP_CHK;
4331
4332 cur_arg++;
4333 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
4334 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
4335 goto error;
4336 }
4337
4338 chk = calloc(1, sizeof(*chk));
4339 if (!chk) {
4340 memprintf(errmsg, "out of memory");
4341 goto error;
4342 }
4343 chk->action = TCPCHK_ACT_ACTION_KW;
4344 chk->action_kw.rule = actrule;
4345 return chk;
4346
4347 error:
4348 free(actrule);
4349 return NULL;
4350}
4351
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004352static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Fauletb7d30092020-03-30 15:19:03 +02004353 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004354{
4355 struct tcpcheck_rule *chk = NULL;
Christopher Faulet5c288742020-03-31 08:15:58 +02004356 struct sockaddr_storage *sk = NULL;
Christopher Faulet98572322020-03-30 13:16:44 +02004357 char *comment = NULL, *sni = NULL, *alpn = NULL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004358 struct sample_expr *port_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004359 unsigned short conn_opts = 0;
4360 long port = 0;
Christopher Faulet98572322020-03-30 13:16:44 +02004361 int alpn_len = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004362
4363 list_for_each_entry(chk, rules, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004364 if (chk->action != TCPCHK_ACT_COMMENT && chk->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004365 break;
4366 }
4367 if (&chk->list != rules && chk->action != TCPCHK_ACT_CONNECT) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004368 memprintf(errmsg, "first step MUST also be a 'connect', "
4369 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
4370 "when there is a 'connect' step in the tcp-check ruleset");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004371 goto error;
4372 }
4373
4374 cur_arg++;
4375 while (*(args[cur_arg])) {
Christopher Fauletbb591a12020-04-01 16:52:17 +02004376 if (strcmp(args[cur_arg], "default") == 0)
4377 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
Christopher Faulet5c288742020-03-31 08:15:58 +02004378 else if (strcmp(args[cur_arg], "addr") == 0) {
4379 int port1, port2;
4380 struct protocol *proto;
4381
4382 if (!*(args[cur_arg+1])) {
4383 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
4384 goto error;
4385 }
4386
4387 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
4388 if (!sk) {
4389 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
4390 goto error;
4391 }
4392
4393 proto = protocol_by_family(sk->ss_family);
4394 if (!proto || !proto->connect) {
4395 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
4396 args[cur_arg]);
4397 goto error;
4398 }
4399
4400 if (port1 != port2) {
4401 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
4402 args[cur_arg], args[cur_arg+1]);
4403 goto error;
4404 }
4405
4406 cur_arg++;
4407 }
Christopher Faulet4dce5922020-03-30 13:54:42 +02004408 else if (strcmp(args[cur_arg], "port") == 0) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004409 const char *p, *end;
4410
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004411 if (!*(args[cur_arg+1])) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004412 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004413 goto error;
4414 }
4415 cur_arg++;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004416
4417 port = 0;
4418 release_sample_expr(port_expr);
4419 p = args[cur_arg]; end = p + strlen(p);
4420 port = read_uint(&p, end);
4421 if (p != end) {
4422 int idx = 0;
4423
4424 px->conf.args.ctx = ARGC_SRV;
4425 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4426 file, line, errmsg, &px->conf.args, NULL);
4427
4428 if (!port_expr) {
4429 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
4430 goto error;
4431 }
4432 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4433 memprintf(errmsg, "error detected while parsing port expression : "
4434 " fetch method '%s' extracts information from '%s', "
4435 "none of which is available here.\n",
4436 args[cur_arg], sample_src_names(port_expr->fetch->use));
4437 goto error;
4438 }
4439 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
4440 }
4441 else if (port > 65535 || port < 1) {
4442 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
4443 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004444 goto error;
4445 }
4446 }
4447 else if (strcmp(args[cur_arg], "comment") == 0) {
4448 if (!*(args[cur_arg+1])) {
4449 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4450 goto error;
4451 }
4452 cur_arg++;
4453 free(comment);
4454 comment = strdup(args[cur_arg]);
4455 if (!comment) {
4456 memprintf(errmsg, "out of memory");
4457 goto error;
4458 }
4459 }
4460 else if (strcmp(args[cur_arg], "send-proxy") == 0)
4461 conn_opts |= TCPCHK_OPT_SEND_PROXY;
Christopher Faulet085426a2020-03-30 13:07:02 +02004462 else if (strcmp(args[cur_arg], "via-socks4") == 0)
4463 conn_opts |= TCPCHK_OPT_SOCKS4;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004464 else if (strcmp(args[cur_arg], "linger") == 0)
4465 conn_opts |= TCPCHK_OPT_LINGER;
4466#ifdef USE_OPENSSL
4467 else if (strcmp(args[cur_arg], "ssl") == 0) {
4468 px->options |= PR_O_TCPCHK_SSL;
4469 conn_opts |= TCPCHK_OPT_SSL;
4470 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02004471 else if (strcmp(args[cur_arg], "sni") == 0) {
4472 if (!*(args[cur_arg+1])) {
4473 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4474 goto error;
4475 }
4476 cur_arg++;
4477 free(sni);
4478 sni = strdup(args[cur_arg]);
4479 if (!sni) {
4480 memprintf(errmsg, "out of memory");
4481 goto error;
4482 }
4483 }
Christopher Faulet98572322020-03-30 13:16:44 +02004484 else if (strcmp(args[cur_arg], "alpn") == 0) {
4485#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
4486 free(alpn);
4487 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
4488 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
4489 goto error;
4490 }
4491 cur_arg++;
4492#else
4493 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
4494 goto error;
4495#endif
4496 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004497#endif /* USE_OPENSSL */
4498
4499 else {
Christopher Faulet5c288742020-03-31 08:15:58 +02004500 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004501#ifdef USE_OPENSSL
Christopher Faulet98572322020-03-30 13:16:44 +02004502 ", 'ssl', 'sni', 'alpn'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004503#endif /* USE_OPENSSL */
Christopher Faulet4dce5922020-03-30 13:54:42 +02004504 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004505 args[cur_arg]);
4506 goto error;
4507 }
4508 cur_arg++;
4509 }
4510
4511 chk = calloc(1, sizeof(*chk));
4512 if (!chk) {
4513 memprintf(errmsg, "out of memory");
4514 goto error;
4515 }
Gaetan Rivet06d963a2020-02-21 18:49:05 +01004516 chk->action = TCPCHK_ACT_CONNECT;
4517 chk->comment = comment;
4518 chk->connect.port = port;
4519 chk->connect.options = conn_opts;
Christopher Faulet79b31d42020-03-30 13:00:05 +02004520 chk->connect.sni = sni;
Christopher Faulet98572322020-03-30 13:16:44 +02004521 chk->connect.alpn = alpn;
4522 chk->connect.alpn_len= alpn_len;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004523 chk->connect.port_expr= port_expr;
Christopher Faulet5c288742020-03-31 08:15:58 +02004524 if (sk)
4525 chk->connect.addr = *sk;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004526 return chk;
4527
4528 error:
Christopher Faulet98572322020-03-30 13:16:44 +02004529 free(alpn);
Christopher Faulet79b31d42020-03-30 13:00:05 +02004530 free(sni);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004531 free(comment);
Christopher Fauletb7d30092020-03-30 15:19:03 +02004532 release_sample_expr(port_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004533 return NULL;
4534}
4535
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004536static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004537 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004538{
4539 struct tcpcheck_rule *chk = NULL;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004540 char *comment = NULL, *data = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004541 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004542
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004543 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004544 if (!*(args[cur_arg+1])) {
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004545 memprintf(errmsg, "'%s' expects a %s as argument",
4546 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004547 goto error;
4548 }
4549
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004550 data = args[cur_arg+1];
4551
4552 cur_arg += 2;
4553 while (*(args[cur_arg])) {
4554 if (strcmp(args[cur_arg], "comment") == 0) {
4555 if (!*(args[cur_arg+1])) {
4556 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4557 goto error;
4558 }
4559 cur_arg++;
4560 free(comment);
4561 comment = strdup(args[cur_arg]);
4562 if (!comment) {
4563 memprintf(errmsg, "out of memory");
4564 goto error;
4565 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004566 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004567 else if (strcmp(args[cur_arg], "log-format") == 0) {
4568 if (type == TCPCHK_SEND_BINARY)
4569 type = TCPCHK_SEND_BINARY_LF;
4570 else if (type == TCPCHK_SEND_STRING)
4571 type = TCPCHK_SEND_STRING_LF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004572 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004573 else {
4574 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
4575 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004576 goto error;
4577 }
4578 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004579 }
4580
4581 chk = calloc(1, sizeof(*chk));
4582 if (!chk) {
4583 memprintf(errmsg, "out of memory");
4584 goto error;
4585 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004586 chk->action = TCPCHK_ACT_SEND;
4587 chk->comment = comment;
4588 chk->send.type = type;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004589
4590 switch (chk->send.type) {
4591 case TCPCHK_SEND_STRING:
4592 chk->send.data = ist2(strdup(data), strlen(data));
4593 if (!isttest(chk->send.data)) {
4594 memprintf(errmsg, "out of memory");
4595 goto error;
4596 }
4597 break;
4598 case TCPCHK_SEND_BINARY:
4599 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
4600 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
4601 goto error;
4602 }
4603 break;
4604 case TCPCHK_SEND_STRING_LF:
4605 case TCPCHK_SEND_BINARY_LF:
4606 LIST_INIT(&chk->send.fmt);
4607 px->conf.args.ctx = ARGC_SRV;
4608 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4609 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
4610 goto error;
4611 }
4612 break;
4613 case TCPCHK_SEND_UNDEF:
4614 goto error;
4615 }
4616
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004617 return chk;
4618
4619 error:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004620 free(chk);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004621 free(comment);
4622 return NULL;
4623}
4624
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004625static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
4626 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004627{
4628 struct tcpcheck_rule *chk = NULL;
4629 char *comment = NULL;
4630
4631 if (!*(args[cur_arg+1])) {
4632 memprintf(errmsg, "expects a string as argument");
4633 goto error;
4634 }
4635 cur_arg++;
4636 comment = strdup(args[cur_arg]);
4637 if (!comment) {
4638 memprintf(errmsg, "out of memory");
4639 goto error;
4640 }
4641
4642 chk = calloc(1, sizeof(*chk));
4643 if (!chk) {
4644 memprintf(errmsg, "out of memory");
4645 goto error;
4646 }
4647 chk->action = TCPCHK_ACT_COMMENT;
4648 chk->comment = comment;
4649 return chk;
4650
4651 error:
4652 free(comment);
4653 return NULL;
4654}
4655
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004656static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px, struct list *rules,
4657 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004658{
4659 struct tcpcheck_rule *prev_check, *chk = NULL;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004660 struct sample_expr *status_expr = NULL;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004661 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004662 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004663 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
4664 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004665 long min_recv = -1;
4666 int inverse = 0, with_capture = 0;
4667
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004668 str = on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004669 if (!*(args[cur_arg+1])) {
4670 memprintf(errmsg, "expects at least a matching pattern as arguments");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004671 goto error;
4672 }
4673
4674 cur_arg++;
4675 while (*(args[cur_arg])) {
4676 int in_pattern = 0;
4677
4678 rescan:
4679 if (strcmp(args[cur_arg], "min-recv") == 0) {
4680 if (in_pattern) {
4681 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4682 goto error;
4683 }
4684 if (!*(args[cur_arg+1])) {
4685 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
4686 goto error;
4687 }
4688 /* Use an signed integer here because of chksize */
4689 cur_arg++;
4690 min_recv = atol(args[cur_arg]);
4691 if (min_recv < -1 || min_recv > INT_MAX) {
4692 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
4693 goto error;
4694 }
4695 }
4696 else if (*(args[cur_arg]) == '!') {
4697 in_pattern = 1;
4698 while (*(args[cur_arg]) == '!') {
4699 inverse = !inverse;
4700 args[cur_arg]++;
4701 }
4702 if (!*(args[cur_arg]))
4703 cur_arg++;
4704 goto rescan;
4705 }
4706 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "binary") == 0 ||
4707 strcmp(args[cur_arg], "rstring") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
4708 if (type != TCPCHK_EXPECT_UNDEF) {
4709 memprintf(errmsg, "only on pattern expected");
4710 goto error;
4711 }
4712 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING :
4713 ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY :
4714 ((*(args[cur_arg]+1) == 's') ? TCPCHK_EXPECT_REGEX : TCPCHK_EXPECT_REGEX_BINARY)));
4715
4716 if (!*(args[cur_arg+1])) {
4717 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4718 goto error;
4719 }
4720 cur_arg++;
4721 pattern = args[cur_arg];
4722 }
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004723 else if (strcmp(args[cur_arg], "custom") == 0) {
4724 if (in_pattern) {
4725 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4726 goto error;
4727 }
4728 if (type != TCPCHK_EXPECT_UNDEF) {
4729 memprintf(errmsg, "only on pattern expected");
4730 goto error;
4731 }
4732 type = TCPCHK_EXPECT_CUSTOM;
4733 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004734 else if (strcmp(args[cur_arg], "comment") == 0) {
4735 if (in_pattern) {
4736 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4737 goto error;
4738 }
4739 if (!*(args[cur_arg+1])) {
4740 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4741 goto error;
4742 }
4743 cur_arg++;
4744 free(comment);
4745 comment = strdup(args[cur_arg]);
4746 if (!comment) {
4747 memprintf(errmsg, "out of memory");
4748 goto error;
4749 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004750 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004751 else if (strcmp(args[cur_arg], "on-success") == 0) {
4752 if (in_pattern) {
4753 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4754 goto error;
4755 }
4756 if (!*(args[cur_arg+1])) {
4757 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4758 goto error;
4759 }
4760 cur_arg++;
4761 free(on_success_msg);
4762 on_success_msg = strdup(args[cur_arg]);
4763 if (!on_success_msg) {
4764 memprintf(errmsg, "out of memory");
4765 goto error;
4766 }
4767 }
4768 else if (strcmp(args[cur_arg], "on-error") == 0) {
4769 if (in_pattern) {
4770 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4771 goto error;
4772 }
4773 if (!*(args[cur_arg+1])) {
4774 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4775 goto error;
4776 }
4777 cur_arg++;
4778 free(on_error_msg);
4779 on_error_msg = strdup(args[cur_arg]);
4780 if (!on_error_msg) {
4781 memprintf(errmsg, "out of memory");
4782 goto error;
4783 }
4784 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004785 else if (strcmp(args[cur_arg], "error-status") == 0) {
4786 if (in_pattern) {
4787 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4788 goto error;
4789 }
4790 if (!*(args[cur_arg+1])) {
4791 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4792 goto error;
4793 }
4794 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4795 err_st = HCHK_STATUS_L7RSP;
4796 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4797 err_st = HCHK_STATUS_L7STS;
4798 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4799 err_st = HCHK_STATUS_L6RSP;
4800 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4801 err_st = HCHK_STATUS_L4CON;
4802 else {
4803 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4804 args[cur_arg], args[cur_arg+1]);
4805 goto error;
4806 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004807 cur_arg++;
4808 }
4809 else if (strcmp(args[cur_arg], "status-code") == 0) {
4810 int idx = 0;
4811
4812 if (in_pattern) {
4813 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4814 goto error;
4815 }
4816 if (!*(args[cur_arg+1])) {
4817 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4818 goto error;
4819 }
4820
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004821 cur_arg++;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004822 release_sample_expr(status_expr);
4823 px->conf.args.ctx = ARGC_SRV;
4824 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4825 file, line, errmsg, &px->conf.args, NULL);
4826 if (!status_expr) {
4827 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4828 goto error;
4829 }
4830 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4831 memprintf(errmsg, "error detected while parsing status-code expression : "
4832 " fetch method '%s' extracts information from '%s', "
4833 "none of which is available here.\n",
4834 args[cur_arg], sample_src_names(status_expr->fetch->use));
4835 goto error;
4836 }
4837 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004838 }
4839 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4840 if (in_pattern) {
4841 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4842 goto error;
4843 }
4844 if (!*(args[cur_arg+1])) {
4845 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4846 goto error;
4847 }
4848 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4849 tout_st = HCHK_STATUS_L7TOUT;
4850 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4851 tout_st = HCHK_STATUS_L6TOUT;
4852 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4853 tout_st = HCHK_STATUS_L4TOUT;
4854 else {
4855 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4856 args[cur_arg], args[cur_arg+1]);
4857 goto error;
4858 }
4859 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004860 }
4861 else {
4862 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4863 " or comment but got '%s' as argument.", args[cur_arg]);
4864 goto error;
4865 }
4866
4867 cur_arg++;
4868 }
4869
4870 if (comment) {
4871 char *p = comment;
4872
4873 while (*p) {
4874 if (*p == '\\') {
4875 p++;
4876 if (!*p || !isdigit((unsigned char)*p) ||
4877 (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
4878 memprintf(errmsg, "invalid backreference in 'comment' argument");
4879 goto error;
4880 }
4881 with_capture = 1;
4882 }
4883 p++;
4884 }
4885 if (with_capture && !inverse)
4886 memprintf(errmsg, "using backreference in a positive expect comment is useless");
4887 }
4888
4889 chk = calloc(1, sizeof(*chk));
4890 if (!chk) {
4891 memprintf(errmsg, "out of memory");
4892 goto error;
4893 }
4894 chk->action = TCPCHK_ACT_EXPECT;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004895 LIST_INIT(&chk->expect.onerror_fmt);
4896 LIST_INIT(&chk->expect.onsuccess_fmt);
4897 chk->comment = comment; comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004898 chk->expect.type = type;
4899 chk->expect.min_recv = min_recv;
4900 chk->expect.inverse = inverse;
4901 chk->expect.with_capture = with_capture;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004902 chk->expect.err_status = err_st;
4903 chk->expect.tout_status = tout_st;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004904 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004905
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004906 if (on_success_msg) {
4907 px->conf.args.ctx = ARGC_SRV;
4908 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4909 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4910 goto error;
4911 }
4912 free(on_success_msg);
4913 on_success_msg = NULL;
4914 }
4915 if (on_error_msg) {
4916 px->conf.args.ctx = ARGC_SRV;
4917 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4918 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4919 goto error;
4920 }
4921 free(on_error_msg);
4922 on_error_msg = NULL;
4923 }
4924
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004925 switch (chk->expect.type) {
4926 case TCPCHK_EXPECT_STRING:
4927 chk->expect.string = strdup(pattern);
4928 chk->expect.length = strlen(pattern);
4929 if (!chk->expect.string) {
4930 memprintf(errmsg, "out of memory");
4931 goto error;
4932 }
4933 break;
4934 case TCPCHK_EXPECT_BINARY:
4935 if (parse_binary(pattern, &chk->expect.string, &chk->expect.length, errmsg) == 0) {
4936 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
4937 goto error;
4938 }
4939 case TCPCHK_EXPECT_REGEX:
4940 case TCPCHK_EXPECT_REGEX_BINARY:
4941 chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
4942 if (!chk->expect.regex)
4943 goto error;
4944 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004945 case TCPCHK_EXPECT_CUSTOM:
4946 chk->expect.custom = NULL; /* Must be defined by the caller ! */
4947 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004948 case TCPCHK_EXPECT_UNDEF:
4949 free(chk);
4950 memprintf(errmsg, "pattern not found");
4951 goto error;
4952 }
4953
4954 /* All tcp-check expect points back to the first inverse expect rule in
4955 * a chain of one or more expect rule, potentially itself.
4956 */
4957 chk->expect.head = chk;
4958 list_for_each_entry_rev(prev_check, rules, list) {
4959 if (prev_check->action == TCPCHK_ACT_EXPECT) {
4960 if (prev_check->expect.inverse)
4961 chk->expect.head = prev_check;
4962 continue;
4963 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01004964 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004965 break;
4966 }
4967 return chk;
4968
4969 error:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004970 free_tcpcheck(chk, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004971 free(str);
4972 free(comment);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004973 free(on_success_msg);
4974 free(on_error_msg);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004975 release_sample_expr(status_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004976 return NULL;
4977}
4978
4979/* Parses the "tcp-check" proxy keyword */
4980static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
4981 struct proxy *defpx, const char *file, int line,
4982 char **errmsg)
4983{
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004984 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004985 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01004986 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004987
4988 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
4989 ret = 1;
4990
4991 if (curpx == defpx) {
4992 memprintf(errmsg, "'%s' not allowed in 'defaults' section.", args[0]);
4993 goto error;
4994 }
4995
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004996 if (rules->flags & TCPCHK_RULES_DEF) {
4997 /* Only shared ruleset can be inherited from the default section */
4998 rules->flags = 0;
4999 rules->list = NULL;
5000 }
5001 if (rules->list && (rules->flags & TCPCHK_RULES_SHARED)) {
5002 memprintf(errmsg, "%s : A shared tcp-check ruleset already configured.", args[0]);
5003 goto error;
5004 }
5005
5006 if (!rules->list) {
5007 rules->list = calloc(1, sizeof(*rules->list));
5008 if (!rules->list) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005009 memprintf(errmsg, "%s : out of memory.", args[0]);
5010 goto error;
5011 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005012 LIST_INIT(rules->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005013 }
5014
Gaetan Rivet5301b012020-02-25 17:19:17 +01005015 index = 0;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005016 if (!LIST_ISEMPTY(rules->list)) {
5017 chk = LIST_PREV(rules->list, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005018 index = chk->index + 1;
5019 }
5020
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005021 cur_arg = 1;
5022 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005023 chk = parse_tcpcheck_connect(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005024 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005025 chk = parse_tcpcheck_send(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005026 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005027 chk = parse_tcpcheck_expect(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005028 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005029 chk = parse_tcpcheck_comment(args, cur_arg, curpx, rules->list, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005030 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005031 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5032
5033 if (!kw) {
5034 action_kw_tcp_check_build_list(&trash);
5035 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5036 "%s%s. but got '%s'",
5037 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5038 goto error;
5039 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005040 chk = parse_tcpcheck_action(args, cur_arg, curpx, rules->list, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005041 }
5042
5043 if (!chk) {
5044 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5045 goto error;
5046 }
5047 ret = (*errmsg != NULL); /* Handle warning */
5048
5049 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005050 chk->index = index;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005051 LIST_ADDQ(rules->list, &chk->list);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005052 return ret;
5053
5054 error:
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005055 deinit_proxy_tcpcheck(curpx);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005056 return -1;
5057}
5058
Christopher Faulet33f05df2020-04-01 11:08:50 +02005059
5060static struct tcpcheck_ruleset *tcpcheck_ruleset_lookup(const char *name)
5061{
5062 struct tcpcheck_ruleset *rs;
5063
5064 list_for_each_entry(rs, &tcpchecks_list, list) {
5065 if (strcmp(rs->name, name) == 0)
5066 return rs;
5067 }
5068 return NULL;
5069}
5070
5071static struct tcpcheck_ruleset *tcpcheck_ruleset_create(const char *name)
5072{
5073 struct tcpcheck_ruleset *rs;
5074
5075 rs = calloc(1, sizeof(*rs));
5076 if (rs == NULL)
5077 return NULL;
5078
5079 rs->name = strdup(name);
5080 if (rs->name == NULL) {
5081 free(rs);
5082 return NULL;
5083 }
5084
5085 LIST_INIT(&rs->list);
5086 LIST_INIT(&rs->rules);
5087 LIST_ADDQ(&tcpchecks_list, &rs->list);
5088 return rs;
5089}
5090
5091static void tcpcheck_ruleset_release(struct tcpcheck_ruleset *rs)
5092{
5093 struct tcpcheck_rule *r, *rb;
5094 if (!rs)
5095 return;
5096
5097 LIST_DEL(&rs->list);
5098 list_for_each_entry_safe(r, rb, &rs->rules, list) {
5099 LIST_DEL(&r->list);
5100 free_tcpcheck(r, 0);
5101 }
5102 free(rs->name);
5103 free(rs);
5104}
5105
5106
5107/* Parses the "option redis-check" proxy keyword */
5108int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5109 const char *file, int line)
5110{
5111 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5112 static char *redis_res = "+PONG\r\n";
5113
5114 struct tcpcheck_ruleset *rs = NULL;
5115 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5116 struct tcpcheck_rule *chk;
5117 char *errmsg = NULL;
5118 int err_code = 0;
5119
5120 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5121 err_code |= ERR_WARN;
5122
5123 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5124 goto out;
5125
5126 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5127 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5128 file, line);
5129 err_code |= ERR_ALERT | ERR_FATAL;
5130 goto out;
5131 }
5132
5133 curpx->options2 &= ~PR_O2_CHK_ANY;
5134 curpx->options2 |= PR_O2_TCPCHK_CHK;
5135
5136 free_tcpcheck_vars(&rules->preset_vars);
5137 rules->list = NULL;
5138 rules->flags = 0;
5139
5140 rs = tcpcheck_ruleset_lookup("*redis-check");
5141 if (rs)
5142 goto ruleset_found;
5143
5144 rs = tcpcheck_ruleset_create("*redis-check");
5145 if (rs == NULL) {
5146 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5147 goto error;
5148 }
5149
5150 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5151 1, curpx, &rs->rules, file, line, &errmsg);
5152 if (!chk) {
5153 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5154 goto error;
5155 }
5156 chk->index = 0;
5157 LIST_ADDQ(&rs->rules, &chk->list);
5158
5159 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5160 "error-status", "L7STS",
5161 "on-error", "%[check.payload(),cut_crlf]",
5162 "on-success", "Redis server is ok",
5163 ""},
5164 1, curpx, &rs->rules, file, line, &errmsg);
5165 if (!chk) {
5166 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5167 goto error;
5168 }
5169 chk->index = 1;
5170 LIST_ADDQ(&rs->rules, &chk->list);
5171
5172 LIST_ADDQ(&tcpchecks_list, &rs->list);
5173
5174 ruleset_found:
5175 rules->list = &rs->rules;
5176 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_REDIS_CHK);
5177
5178 out:
5179 free(errmsg);
5180 return err_code;
5181
5182 error:
5183 tcpcheck_ruleset_release(rs);
5184 err_code |= ERR_ALERT | ERR_FATAL;
5185 goto out;
5186}
5187
Christopher Faulet811f78c2020-04-01 11:10:27 +02005188
5189/* Parses the "option ssl-hello-chk" proxy keyword */
5190int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5191 const char *file, int line)
5192{
5193 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5194 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5195 *
5196 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5197 */
5198 static char sslv3_client_hello[] = {
5199 "16" /* ContentType : 0x16 = Hanshake */
5200 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5201 "0079" /* ContentLength : 0x79 bytes after this one */
5202 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5203 "000075" /* HandshakeLength : 0x75 bytes after this one */
5204 "0300" /* Hello Version : 0x0300 = v3 */
5205 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5206 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5207 "00" /* Session ID length : empty (no session ID) */
5208 "004E" /* Cipher Suite Length : 78 bytes after this one */
5209 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5210 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5211 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5212 "000D" "000E" "000F" "0010" /* various bit lengths, */
5213 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5214 "0015" "0016" "0017" "0018"
5215 "0019" "001A" "001B" "002F"
5216 "0030" "0031" "0032" "0033"
5217 "0034" "0035" "0036" "0037"
5218 "0038" "0039" "003A"
5219 "01" /* Compression Length : 0x01 = 1 byte for types */
5220 "00" /* Compression Type : 0x00 = NULL compression */
5221 };
5222
5223 struct tcpcheck_ruleset *rs = NULL;
5224 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5225 struct tcpcheck_rule *chk;
5226 char *errmsg = NULL;
5227 int err_code = 0;
5228
5229 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5230 err_code |= ERR_WARN;
5231
5232 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5233 goto out;
5234
5235 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5236 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5237 file, line);
5238 err_code |= ERR_ALERT | ERR_FATAL;
5239 goto out;
5240 }
5241
5242 curpx->options2 &= ~PR_O2_CHK_ANY;
5243 curpx->options2 |= PR_O2_TCPCHK_CHK;
5244
5245 free_tcpcheck_vars(&rules->preset_vars);
5246 rules->list = NULL;
5247 rules->flags = 0;
5248
5249 rs = tcpcheck_ruleset_lookup("*ssl-hello-check");
5250 if (rs)
5251 goto ruleset_found;
5252
5253 rs = tcpcheck_ruleset_create("*ssl-hello-check");
5254 if (rs == NULL) {
5255 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5256 goto error;
5257 }
5258
5259 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5260 1, curpx, &rs->rules, file, line, &errmsg);
5261 if (!chk) {
5262 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5263 goto error;
5264 }
5265 chk->index = 0;
5266 LIST_ADDQ(&rs->rules, &chk->list);
5267
5268 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
5269 "min-recv", "5",
5270 "error-status", "L6RSP", "tout-status", "L6TOUT",
5271 ""},
5272 1, curpx, &rs->rules, file, line, &errmsg);
5273 if (!chk) {
5274 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5275 goto error;
5276 }
5277 chk->index = 1;
5278 LIST_ADDQ(&rs->rules, &chk->list);
5279
5280 LIST_ADDQ(&tcpchecks_list, &rs->list);
5281
5282 ruleset_found:
5283 rules->list = &rs->rules;
5284 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SSL3_CHK);
5285
5286 out:
5287 free(errmsg);
5288 return err_code;
5289
5290 error:
5291 tcpcheck_ruleset_release(rs);
5292 err_code |= ERR_ALERT | ERR_FATAL;
5293 goto out;
5294}
5295
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005296/* Parses the "option smtpchk" proxy keyword */
5297int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5298 const char *file, int line)
5299{
5300 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5301
5302 struct tcpcheck_ruleset *rs = NULL;
5303 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5304 struct tcpcheck_rule *chk;
5305 struct tcpcheck_var *var = NULL;
5306 char *cmd = NULL, *errmsg = NULL;
5307 int err_code = 0;
5308
5309 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5310 err_code |= ERR_WARN;
5311
5312 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5313 goto out;
5314
5315 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5316 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5317 file, line);
5318 err_code |= ERR_ALERT | ERR_FATAL;
5319 goto out;
5320 }
5321
5322 curpx->options2 &= ~PR_O2_CHK_ANY;
5323 curpx->options2 |= PR_O2_TCPCHK_CHK;
5324
5325 free_tcpcheck_vars(&rules->preset_vars);
5326 rules->list = NULL;
5327 rules->flags = 0;
5328
5329 cur_arg += 2;
5330 if (*args[cur_arg] && *args[cur_arg+1] &&
5331 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5332 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5333 if (cmd)
5334 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
5335 }
5336 else {
5337 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
5338 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
5339 cmd = strdup("HELO localhost");
5340 }
5341
5342 var = tcpcheck_var_create("check.smtp_cmd");
5343 if (cmd == NULL || var == NULL) {
5344 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5345 goto error;
5346 }
5347 var->data.type = SMP_T_STR;
5348 var->data.u.str.area = cmd;
5349 var->data.u.str.data = strlen(cmd);
5350 LIST_INIT(&var->list);
5351 LIST_ADDQ(&rules->preset_vars, &var->list);
5352 cmd = NULL;
5353 var = NULL;
5354
5355 rs = tcpcheck_ruleset_lookup("*smtp-check");
5356 if (rs)
5357 goto ruleset_found;
5358
5359 rs = tcpcheck_ruleset_create("*smtp-check");
5360 if (rs == NULL) {
5361 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5362 goto error;
5363 }
5364
5365 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5366 1, curpx, &rs->rules, file, line, &errmsg);
5367 if (!chk) {
5368 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5369 goto error;
5370 }
5371 chk->index = 0;
5372 LIST_ADDQ(&rs->rules, &chk->list);
5373
5374 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
5375 "min-recv", "4",
5376 "error-status", "L7RSP",
5377 "on-error", "%[check.payload(),cut_crlf]",
5378 ""},
5379 1, curpx, &rs->rules, file, line, &errmsg);
5380 if (!chk) {
5381 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5382 goto error;
5383 }
5384 chk->index = 1;
5385 LIST_ADDQ(&rs->rules, &chk->list);
5386
5387 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
5388 "min-recv", "4",
5389 "error-status", "L7STS",
5390 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5391 "status-code", "check.payload(0,3)",
5392 ""},
5393 1, curpx, &rs->rules, file, line, &errmsg);
5394 if (!chk) {
5395 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5396 goto error;
5397 }
5398 chk->index = 2;
5399 LIST_ADDQ(&rs->rules, &chk->list);
5400
5401 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
5402 1, curpx, &rs->rules, file, line, &errmsg);
5403 if (!chk) {
5404 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5405 goto error;
5406 }
5407 chk->index = 3;
5408 LIST_ADDQ(&rs->rules, &chk->list);
5409
5410 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
5411 "min-recv", "4",
5412 "error-status", "L7STS",
5413 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5414 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5415 "status-code", "check.payload(0,3)",
5416 ""},
5417 1, curpx, &rs->rules, file, line, &errmsg);
5418 if (!chk) {
5419 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5420 goto error;
5421 }
5422 chk->index = 4;
5423 LIST_ADDQ(&rs->rules, &chk->list);
5424
5425 LIST_ADDQ(&tcpchecks_list, &rs->list);
5426
5427 ruleset_found:
5428 rules->list = &rs->rules;
5429 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SMTP_CHK);
5430
5431 out:
5432 free(errmsg);
5433 return err_code;
5434
5435 error:
5436 free(cmd);
5437 free(var);
5438 free_tcpcheck_vars(&rules->preset_vars);
5439 tcpcheck_ruleset_release(rs);
5440 err_code |= ERR_ALERT | ERR_FATAL;
5441 goto out;
5442}
Christopher Faulet811f78c2020-04-01 11:10:27 +02005443
Christopher Fauletce355072020-04-02 11:44:39 +02005444/* Parses the "option pgsql-check" proxy keyword */
5445int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5446 const char *file, int line)
5447{
5448 static char pgsql_req[] = {
5449 "%[var(check.plen),htonl,hex]" /* The packet length*/
5450 "00030000" /* the version 3.0 */
5451 "7573657200" /* "user" key */
5452 "%[var(check.username),hex]00" /* the username */
5453 "00"
5454 };
5455
5456 struct tcpcheck_ruleset *rs = NULL;
5457 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5458 struct tcpcheck_rule *chk;
5459 struct tcpcheck_var *var = NULL;
5460 char *user = NULL, *errmsg = NULL;
5461 size_t packetlen = 0;
5462 int err_code = 0;
5463
5464 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5465 err_code |= ERR_WARN;
5466
5467 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5468 goto out;
5469
5470 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5471 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5472 file, line);
5473 err_code |= ERR_ALERT | ERR_FATAL;
5474 goto out;
5475 }
5476
5477
5478 curpx->options2 &= ~PR_O2_CHK_ANY;
5479 curpx->options2 |= PR_O2_TCPCHK_CHK;
5480
5481 free_tcpcheck_vars(&rules->preset_vars);
5482 rules->list = NULL;
5483 rules->flags = 0;
5484
5485 cur_arg += 2;
5486 if (!*args[cur_arg] || !*args[cur_arg+1]) {
5487 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
5488 file, line, args[0], args[1]);
5489 goto error;
5490 }
5491 if (strcmp(args[cur_arg], "user") == 0) {
5492 packetlen = 15 + strlen(args[cur_arg+1]);
5493 user = strdup(args[cur_arg+1]);
5494
5495 var = tcpcheck_var_create("check.username");
5496 if (user == NULL || var == NULL) {
5497 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5498 goto error;
5499 }
5500 var->data.type = SMP_T_STR;
5501 var->data.u.str.area = user;
5502 var->data.u.str.data = strlen(user);
5503 LIST_INIT(&var->list);
5504 LIST_ADDQ(&rules->preset_vars, &var->list);
5505 user = NULL;
5506 var = NULL;
5507
5508 var = tcpcheck_var_create("check.plen");
5509 if (var == NULL) {
5510 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5511 goto error;
5512 }
5513 var->data.type = SMP_T_SINT;
5514 var->data.u.sint = packetlen;
5515 LIST_INIT(&var->list);
5516 LIST_ADDQ(&rules->preset_vars, &var->list);
5517 var = NULL;
5518 }
5519 else {
5520 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
5521 file, line, args[0], args[1]);
5522 goto error;
5523 }
5524
5525 rs = tcpcheck_ruleset_lookup("*pgsql-check");
5526 if (rs)
5527 goto ruleset_found;
5528
5529 rs = tcpcheck_ruleset_create("*pgsql-check");
5530 if (rs == NULL) {
5531 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5532 goto error;
5533 }
5534
5535 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5536 1, curpx, &rs->rules, file, line, &errmsg);
5537 if (!chk) {
5538 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5539 goto error;
5540 }
5541 chk->index = 0;
5542 LIST_ADDQ(&rs->rules, &chk->list);
5543
5544 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
5545 1, curpx, &rs->rules, file, line, &errmsg);
5546 if (!chk) {
5547 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5548 goto error;
5549 }
5550 chk->index = 1;
5551 LIST_ADDQ(&rs->rules, &chk->list);
5552
5553 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
5554 "min-recv", "5",
5555 "error-status", "L7RSP",
5556 "on-error", "%[check.payload(6,0)]",
5557 ""},
5558 1, curpx, &rs->rules, file, line, &errmsg);
5559 if (!chk) {
5560 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5561 goto error;
5562 }
5563 chk->index = 2;
5564 LIST_ADDQ(&rs->rules, &chk->list);
5565
5566 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
5567 "min-recv", "9",
5568 "error-status", "L7STS",
5569 "on-success", "PostgreSQL server is ok",
5570 "on-error", "PostgreSQL unknown error",
5571 ""},
5572 1, curpx, &rs->rules, file, line, &errmsg);
5573 if (!chk) {
5574 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5575 goto error;
5576 }
5577 chk->index = 3;
5578 LIST_ADDQ(&rs->rules, &chk->list);
5579
5580 LIST_ADDQ(&tcpchecks_list, &rs->list);
5581
5582 ruleset_found:
5583 rules->list = &rs->rules;
5584 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_PGSQL_CHK);
5585
5586 out:
5587 free(errmsg);
5588 return err_code;
5589
5590 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02005591 free(user);
5592 free(var);
5593 free_tcpcheck_vars(&rules->preset_vars);
5594 tcpcheck_ruleset_release(rs);
5595 err_code |= ERR_ALERT | ERR_FATAL;
5596 goto out;
5597}
5598
5599
5600/* Parses the "option mysql-check" proxy keyword */
5601int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5602 const char *file, int line)
5603{
5604 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
5605 * const char mysql40_client_auth_pkt[] = {
5606 * "\x0e\x00\x00" // packet length
5607 * "\x01" // packet number
5608 * "\x00\x00" // client capabilities
5609 * "\x00\x00\x01" // max packet
5610 * "haproxy\x00" // username (null terminated string)
5611 * "\x00" // filler (always 0x00)
5612 * "\x01\x00\x00" // packet length
5613 * "\x00" // packet number
5614 * "\x01" // COM_QUIT command
5615 * };
5616 */
5617 static char mysql40_rsname[] = "*mysql40-check";
5618 static char mysql40_req[] = {
5619 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5620 "0080" /* client capabilities */
5621 "000001" /* max packet */
5622 "%[var(check.username),hex]00" /* the username */
5623 "00" /* filler (always 0x00) */
5624 "010000" /* packet length*/
5625 "00" /* sequence ID */
5626 "01" /* COM_QUIT command */
5627 };
5628
5629 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
5630 * const char mysql41_client_auth_pkt[] = {
5631 * "\x0e\x00\x00\" // packet length
5632 * "\x01" // packet number
5633 * "\x00\x00\x00\x00" // client capabilities
5634 * "\x00\x00\x00\x01" // max packet
5635 * "\x21" // character set (UTF-8)
5636 * char[23] // All zeroes
5637 * "haproxy\x00" // username (null terminated string)
5638 * "\x00" // filler (always 0x00)
5639 * "\x01\x00\x00" // packet length
5640 * "\x00" // packet number
5641 * "\x01" // COM_QUIT command
5642 * };
5643 */
5644 static char mysql41_rsname[] = "*mysql41-check";
5645 static char mysql41_req[] = {
5646 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5647 "00820000" /* client capabilities */
5648 "00800001" /* max packet */
5649 "21" /* character set (UTF-8) */
5650 "000000000000000000000000" /* 23 bytes, al zeroes */
5651 "0000000000000000000000"
5652 "%[var(check.username),hex]00" /* the username */
5653 "00" /* filler (always 0x00) */
5654 "010000" /* packet length*/
5655 "00" /* sequence ID */
5656 "01" /* COM_QUIT command */
5657 };
5658
5659 struct tcpcheck_ruleset *rs = NULL;
5660 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5661 struct tcpcheck_rule *chk;
5662 struct tcpcheck_var *var = NULL;
5663 char *mysql_rsname = "*mysql-check";
5664 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
5665 int index = 0, err_code = 0;
5666
5667 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5668 err_code |= ERR_WARN;
5669
5670 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5671 goto out;
5672
5673 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5674 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5675 file, line);
5676 err_code |= ERR_ALERT | ERR_FATAL;
5677 goto out;
5678 }
5679
5680 curpx->options2 &= ~PR_O2_CHK_ANY;
5681 curpx->options2 |= PR_O2_TCPCHK_CHK;
5682
5683 free_tcpcheck_vars(&rules->preset_vars);
5684 rules->list = NULL;
5685 rules->flags = 0;
5686
5687 cur_arg += 2;
5688 if (*args[cur_arg]) {
5689 char *user;
5690 int packetlen, userlen;
5691
5692 if (strcmp(args[cur_arg], "user") != 0) {
5693 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
5694 file, line, args[0], args[1], args[cur_arg]);
5695 goto error;
5696 }
5697
5698 if (*(args[cur_arg+1]) == 0) {
5699 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
5700 file, line, args[0], args[1], args[cur_arg]);
5701 goto error;
5702 }
5703
5704 hdr = calloc(4, sizeof(*hdr));
5705 user = strdup(args[cur_arg+1]);
5706 userlen = strlen(args[cur_arg+1]);
5707
5708 if (hdr == NULL || user == NULL) {
5709 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5710 goto error;
5711 }
5712
5713 if (*args[cur_arg+2]) {
5714 if (strcmp(args[cur_arg+2], "post-41") != 0) {
5715 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
5716 file, line, args[cur_arg], args[cur_arg+2]);
5717 goto error;
5718 }
5719 packetlen = userlen + 7 + 27;
5720 mysql_req = mysql41_req;
5721 mysql_rsname = mysql41_rsname;
5722 }
5723 else {
5724 packetlen = userlen + 7;
5725 mysql_req = mysql40_req;
5726 mysql_rsname = mysql40_rsname;
5727 }
5728
5729 hdr[0] = (unsigned char)(packetlen & 0xff);
5730 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
5731 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
5732 hdr[3] = 1;
5733
5734 var = tcpcheck_var_create("check.header");
5735 if (var == NULL) {
5736 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5737 goto error;
5738 }
5739 var->data.type = SMP_T_STR;
5740 var->data.u.str.area = hdr;
5741 var->data.u.str.data = 4;
5742 LIST_INIT(&var->list);
5743 LIST_ADDQ(&rules->preset_vars, &var->list);
5744 hdr = NULL;
5745 var = NULL;
5746
5747 var = tcpcheck_var_create("check.username");
5748 if (var == NULL) {
5749 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5750 goto error;
5751 }
5752 var->data.type = SMP_T_STR;
5753 var->data.u.str.area = user;
5754 var->data.u.str.data = strlen(user);
5755 LIST_INIT(&var->list);
5756 LIST_ADDQ(&rules->preset_vars, &var->list);
5757 user = NULL;
5758 var = NULL;
5759 }
5760
5761 rs = tcpcheck_ruleset_lookup(mysql_rsname);
5762 if (rs)
5763 goto ruleset_found;
5764
5765 rs = tcpcheck_ruleset_create(mysql_rsname);
5766 if (rs == NULL) {
5767 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5768 goto error;
5769 }
5770
5771 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5772 1, curpx, &rs->rules, file, line, &errmsg);
5773 if (!chk) {
5774 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5775 goto error;
5776 }
5777 chk->index = index++;
5778 LIST_ADDQ(&rs->rules, &chk->list);
5779
5780 if (mysql_req) {
5781 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
5782 1, curpx, &rs->rules, file, line, &errmsg);
5783 if (!chk) {
5784 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5785 goto error;
5786 }
5787 chk->index = index++;
5788 LIST_ADDQ(&rs->rules, &chk->list);
5789 }
5790
5791 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5792 1, curpx, &rs->rules, file, line, &errmsg);
5793 if (!chk) {
5794 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5795 goto error;
5796 }
5797 chk->expect.custom = tcpcheck_mysql_expect_iniths;
5798 chk->index = index++;
5799 LIST_ADDQ(&rs->rules, &chk->list);
5800
5801 if (mysql_req) {
5802 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5803 1, curpx, &rs->rules, file, line, &errmsg);
5804 if (!chk) {
5805 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5806 goto error;
5807 }
5808 chk->expect.custom = tcpcheck_mysql_expect_ok;
5809 chk->index = index++;
5810 LIST_ADDQ(&rs->rules, &chk->list);
5811 }
5812
5813 LIST_ADDQ(&tcpchecks_list, &rs->list);
5814
5815 ruleset_found:
5816 rules->list = &rs->rules;
5817 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_MYSQL_CHK);
5818
5819 out:
5820 free(errmsg);
5821 return err_code;
5822
5823 error:
5824 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02005825 free(user);
5826 free(var);
5827 free_tcpcheck_vars(&rules->preset_vars);
5828 tcpcheck_ruleset_release(rs);
5829 err_code |= ERR_ALERT | ERR_FATAL;
5830 goto out;
5831}
5832
Christopher Faulet1997eca2020-04-03 23:13:50 +02005833int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5834 const char *file, int line)
5835{
5836 static char *ldap_req = "300C020101600702010304008000";
5837
5838 struct tcpcheck_ruleset *rs = NULL;
5839 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5840 struct tcpcheck_rule *chk;
5841 char *errmsg = NULL;
5842 int err_code = 0;
5843
5844 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5845 err_code |= ERR_WARN;
5846
5847 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5848 goto out;
5849
5850 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5851 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5852 file, line);
5853 err_code |= ERR_ALERT | ERR_FATAL;
5854 goto out;
5855 }
5856
5857 curpx->options2 &= ~PR_O2_CHK_ANY;
5858 curpx->options2 |= PR_O2_TCPCHK_CHK;
5859
5860 free_tcpcheck_vars(&rules->preset_vars);
5861 rules->list = NULL;
5862 rules->flags = 0;
5863
5864 rs = tcpcheck_ruleset_lookup("*ldap-check");
5865 if (rs)
5866 goto ruleset_found;
5867
5868 rs = tcpcheck_ruleset_create("*ldap-check");
5869 if (rs == NULL) {
5870 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5871 goto error;
5872 }
5873
5874 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
5875 1, curpx, &rs->rules, file, line, &errmsg);
5876 if (!chk) {
5877 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5878 goto error;
5879 }
5880 chk->index = 0;
5881 LIST_ADDQ(&rs->rules, &chk->list);
5882
5883 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
5884 "min-recv", "14",
5885 "on-error", "Not LDAPv3 protocol",
5886 ""},
5887 1, curpx, &rs->rules, file, line, &errmsg);
5888 if (!chk) {
5889 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5890 goto error;
5891 }
5892 chk->index = 1;
5893 LIST_ADDQ(&rs->rules, &chk->list);
5894
5895 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
5896 1, curpx, &rs->rules, file, line, &errmsg);
5897 if (!chk) {
5898 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5899 goto error;
5900 }
5901 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
5902 chk->index = 2;
5903 LIST_ADDQ(&rs->rules, &chk->list);
5904
5905 LIST_ADDQ(&tcpchecks_list, &rs->list);
5906
5907 ruleset_found:
5908 rules->list = &rs->rules;
5909 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_LDAP_CHK);
5910
5911 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02005912 free(errmsg);
5913 return err_code;
5914
5915 error:
5916 tcpcheck_ruleset_release(rs);
5917 err_code |= ERR_ALERT | ERR_FATAL;
5918 goto out;
5919}
5920
5921int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5922 const char *file, int line)
5923{
5924 struct tcpcheck_ruleset *rs = NULL;
5925 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5926 struct tcpcheck_rule *chk;
5927 char *spop_req = NULL;
5928 char *errmsg = NULL;
5929 int spop_len = 0, err_code = 0;
5930
5931 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5932 err_code |= ERR_WARN;
5933
5934 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5935 goto out;
5936
5937 if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
5938 ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
5939 file, line);
5940 err_code |= ERR_ALERT | ERR_FATAL;
5941 goto out;
5942 }
5943
5944 curpx->options2 &= ~PR_O2_CHK_ANY;
5945 curpx->options2 |= PR_O2_TCPCHK_CHK;
5946
5947 free_tcpcheck_vars(&rules->preset_vars);
5948 rules->list = NULL;
5949 rules->flags = 0;
5950
5951
5952 rs = tcpcheck_ruleset_lookup("*spop-check");
5953 if (rs)
5954 goto ruleset_found;
5955
5956 rs = tcpcheck_ruleset_create("*spop-check");
5957 if (rs == NULL) {
5958 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5959 goto error;
5960 }
5961
5962 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
5963 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5964 goto error;
5965 }
5966 chunk_reset(&trash);
5967 dump_binary(&trash, spop_req, spop_len);
5968 trash.area[trash.data] = '\0';
5969
5970 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
5971 1, curpx, &rs->rules, file, line, &errmsg);
5972 if (!chk) {
5973 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5974 goto error;
5975 }
5976 chk->index = 0;
5977 LIST_ADDQ(&rs->rules, &chk->list);
5978
5979 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
5980 1, curpx, &rs->rules, file, line, &errmsg);
5981 if (!chk) {
5982 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5983 goto error;
5984 }
5985 chk->expect.custom = tcpcheck_spop_expect_agenthello;
5986 chk->index = 1;
5987 LIST_ADDQ(&rs->rules, &chk->list);
5988
5989 LIST_ADDQ(&tcpchecks_list, &rs->list);
5990
5991 ruleset_found:
5992 rules->list = &rs->rules;
5993 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SPOP_CHK);
5994
5995 out:
5996 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02005997 free(errmsg);
5998 return err_code;
5999
6000 error:
6001 tcpcheck_ruleset_release(rs);
6002 err_code |= ERR_ALERT | ERR_FATAL;
6003 goto out;
6004}
Christopher Fauletce355072020-04-02 11:44:39 +02006005
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006006
Christopher Fauletce8111e2020-04-06 15:04:11 +02006007/* Parse the "addr" server keyword */
6008static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6009 char **errmsg)
6010{
6011 struct sockaddr_storage *sk;
6012 struct protocol *proto;
6013 int port1, port2, err_code = 0;
6014
6015
6016 if (!*args[*cur_arg+1]) {
6017 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6018 goto error;
6019 }
6020
6021 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6022 if (!sk) {
6023 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6024 goto error;
6025 }
6026
6027 proto = protocol_by_family(sk->ss_family);
6028 if (!proto || !proto->connect) {
6029 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6030 args[*cur_arg], args[*cur_arg+1]);
6031 goto error;
6032 }
6033
6034 if (port1 != port2) {
6035 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6036 args[*cur_arg], args[*cur_arg+1]);
6037 goto error;
6038 }
6039
6040 srv->check.addr = srv->agent.addr = *sk;
6041 srv->flags |= SRV_F_CHECKADDR;
6042 srv->flags |= SRV_F_AGENTADDR;
6043
6044 out:
6045 return err_code;
6046
6047 error:
6048 err_code |= ERR_ALERT | ERR_FATAL;
6049 goto out;
6050}
6051
6052
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006053/* Parse the "agent-addr" server keyword */
6054static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6055 char **errmsg)
6056{
6057 int err_code = 0;
6058
6059 if (!*(args[*cur_arg+1])) {
6060 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6061 goto error;
6062 }
6063 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6064 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6065 goto error;
6066 }
6067
6068 out:
6069 return err_code;
6070
6071 error:
6072 err_code |= ERR_ALERT | ERR_FATAL;
6073 goto out;
6074}
6075
6076/* Parse the "agent-check" server keyword */
6077static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6078 char **errmsg)
6079{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006080 struct tcpcheck_ruleset *rs = NULL;
6081 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6082 struct tcpcheck_rule *chk;
6083 int err_code = 0;
6084
6085 if (srv->do_agent)
6086 goto out;
6087
6088 if (!rules) {
6089 rules = calloc(1, sizeof(*rules));
6090 if (!rules) {
6091 memprintf(errmsg, "out of memory.");
6092 goto error;
6093 }
6094 LIST_INIT(&rules->preset_vars);
6095 srv->agent.tcpcheck_rules = rules;
6096 }
6097 rules->list = NULL;
6098 rules->flags = 0;
6099
6100 rs = tcpcheck_ruleset_lookup("*agent-check");
6101 if (rs)
6102 goto ruleset_found;
6103
6104 rs = tcpcheck_ruleset_create("*agent-check");
6105 if (rs == NULL) {
6106 memprintf(errmsg, "out of memory.");
6107 goto error;
6108 }
6109
6110 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6111 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6112 if (!chk) {
6113 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6114 goto error;
6115 }
6116 chk->index = 0;
6117 LIST_ADDQ(&rs->rules, &chk->list);
6118
6119 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6120 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6121 if (!chk) {
6122 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6123 goto error;
6124 }
6125 chk->expect.custom = tcpcheck_agent_expect_reply;
6126 chk->index = 1;
6127 LIST_ADDQ(&rs->rules, &chk->list);
6128
6129 LIST_ADDQ(&tcpchecks_list, &rs->list);
6130
6131 ruleset_found:
6132 rules->list = &rs->rules;
6133 rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_AGENT_CHK);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006134 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006135
6136 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006137 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006138
6139 error:
6140 deinit_srv_agent_check(srv);
6141 tcpcheck_ruleset_release(rs);
6142 err_code |= ERR_ALERT | ERR_FATAL;
6143 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006144}
6145
6146/* Parse the "agent-inter" server keyword */
6147static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6148 char **errmsg)
6149{
6150 const char *err = NULL;
6151 unsigned int delay;
6152 int err_code = 0;
6153
6154 if (!*(args[*cur_arg+1])) {
6155 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6156 goto error;
6157 }
6158
6159 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6160 if (err == PARSE_TIME_OVER) {
6161 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6162 args[*cur_arg+1], args[*cur_arg], srv->id);
6163 goto error;
6164 }
6165 else if (err == PARSE_TIME_UNDER) {
6166 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6167 args[*cur_arg+1], args[*cur_arg], srv->id);
6168 goto error;
6169 }
6170 else if (err) {
6171 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6172 *err, srv->id);
6173 goto error;
6174 }
6175 if (delay <= 0) {
6176 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6177 delay, args[*cur_arg], srv->id);
6178 goto error;
6179 }
6180 srv->agent.inter = delay;
6181
6182 out:
6183 return err_code;
6184
6185 error:
6186 err_code |= ERR_ALERT | ERR_FATAL;
6187 goto out;
6188}
6189
6190/* Parse the "agent-port" server keyword */
6191static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6192 char **errmsg)
6193{
6194 int err_code = 0;
6195
6196 if (!*(args[*cur_arg+1])) {
6197 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6198 goto error;
6199 }
6200
6201 global.maxsock++;
6202 srv->agent.port = atol(args[*cur_arg+1]);
6203
6204 out:
6205 return err_code;
6206
6207 error:
6208 err_code |= ERR_ALERT | ERR_FATAL;
6209 goto out;
6210}
6211
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006212int set_srv_agent_send(struct server *srv, const char *send)
6213{
6214 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6215 struct tcpcheck_var *var = NULL;
6216 char *str;
6217
6218 str = strdup(send);
6219 var = tcpcheck_var_create("check.agent_string");
6220 if (str == NULL || var == NULL)
6221 goto error;
6222
6223 free_tcpcheck_vars(&rules->preset_vars);
6224
6225 var->data.type = SMP_T_STR;
6226 var->data.u.str.area = str;
6227 var->data.u.str.data = strlen(str);
6228 LIST_INIT(&var->list);
6229 LIST_ADDQ(&rules->preset_vars, &var->list);
6230
6231 return 1;
6232
6233 error:
6234 free(str);
6235 free(var);
6236 return 0;
6237}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006238
6239/* Parse the "agent-send" server keyword */
6240static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6241 char **errmsg)
6242{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006243 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006244 int err_code = 0;
6245
6246 if (!*(args[*cur_arg+1])) {
6247 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
6248 goto error;
6249 }
6250
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006251 if (!rules) {
6252 rules = calloc(1, sizeof(*rules));
6253 if (!rules) {
6254 memprintf(errmsg, "out of memory.");
6255 goto error;
6256 }
6257 LIST_INIT(&rules->preset_vars);
6258 srv->agent.tcpcheck_rules = rules;
6259 }
6260
6261 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006262 memprintf(errmsg, "out of memory.");
6263 goto error;
6264 }
6265
6266 out:
6267 return err_code;
6268
6269 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006270 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006271 err_code |= ERR_ALERT | ERR_FATAL;
6272 goto out;
6273}
6274
6275/* Parse the "no-agent-send" server keyword */
6276static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6277 char **errmsg)
6278{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006279 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006280 return 0;
6281}
6282
Christopher Fauletce8111e2020-04-06 15:04:11 +02006283/* Parse the "check" server keyword */
6284static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6285 char **errmsg)
6286{
6287 srv->do_check = 1;
6288 return 0;
6289}
6290
6291/* Parse the "check-send-proxy" server keyword */
6292static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6293 char **errmsg)
6294{
6295 srv->check.send_proxy = 1;
6296 return 0;
6297}
6298
6299/* Parse the "check-via-socks4" server keyword */
6300static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6301 char **errmsg)
6302{
6303 srv->check.via_socks4 = 1;
6304 return 0;
6305}
6306
6307/* Parse the "no-check" server keyword */
6308static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6309 char **errmsg)
6310{
6311 deinit_srv_check(srv);
6312 return 0;
6313}
6314
6315/* Parse the "no-check-send-proxy" server keyword */
6316static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6317 char **errmsg)
6318{
6319 srv->check.send_proxy = 0;
6320 return 0;
6321}
6322
6323/* Parse the "rise" server keyword */
6324static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6325 char **errmsg)
6326{
6327 int err_code = 0;
6328
6329 if (!*args[*cur_arg + 1]) {
6330 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6331 goto error;
6332 }
6333
6334 srv->check.rise = atol(args[*cur_arg+1]);
6335 if (srv->check.rise <= 0) {
6336 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6337 goto error;
6338 }
6339
6340 if (srv->check.health)
6341 srv->check.health = srv->check.rise;
6342
6343 out:
6344 return err_code;
6345
6346 error:
6347 deinit_srv_agent_check(srv);
6348 err_code |= ERR_ALERT | ERR_FATAL;
6349 goto out;
6350 return 0;
6351}
6352
6353/* Parse the "fall" server keyword */
6354static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6355 char **errmsg)
6356{
6357 int err_code = 0;
6358
6359 if (!*args[*cur_arg + 1]) {
6360 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6361 goto error;
6362 }
6363
6364 srv->check.fall = atol(args[*cur_arg+1]);
6365 if (srv->check.fall <= 0) {
6366 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6367 goto error;
6368 }
6369
6370 out:
6371 return err_code;
6372
6373 error:
6374 deinit_srv_agent_check(srv);
6375 err_code |= ERR_ALERT | ERR_FATAL;
6376 goto out;
6377 return 0;
6378}
6379
6380/* Parse the "inter" server keyword */
6381static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6382 char **errmsg)
6383{
6384 const char *err = NULL;
6385 unsigned int delay;
6386 int err_code = 0;
6387
6388 if (!*(args[*cur_arg+1])) {
6389 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6390 goto error;
6391 }
6392
6393 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6394 if (err == PARSE_TIME_OVER) {
6395 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6396 args[*cur_arg+1], args[*cur_arg], srv->id);
6397 goto error;
6398 }
6399 else if (err == PARSE_TIME_UNDER) {
6400 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6401 args[*cur_arg+1], args[*cur_arg], srv->id);
6402 goto error;
6403 }
6404 else if (err) {
6405 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6406 *err, srv->id);
6407 goto error;
6408 }
6409 if (delay <= 0) {
6410 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6411 delay, args[*cur_arg], srv->id);
6412 goto error;
6413 }
6414 srv->check.inter = delay;
6415
6416 out:
6417 return err_code;
6418
6419 error:
6420 err_code |= ERR_ALERT | ERR_FATAL;
6421 goto out;
6422}
6423
6424
6425/* Parse the "fastinter" server keyword */
6426static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6427 char **errmsg)
6428{
6429 const char *err = NULL;
6430 unsigned int delay;
6431 int err_code = 0;
6432
6433 if (!*(args[*cur_arg+1])) {
6434 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6435 goto error;
6436 }
6437
6438 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6439 if (err == PARSE_TIME_OVER) {
6440 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6441 args[*cur_arg+1], args[*cur_arg], srv->id);
6442 goto error;
6443 }
6444 else if (err == PARSE_TIME_UNDER) {
6445 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6446 args[*cur_arg+1], args[*cur_arg], srv->id);
6447 goto error;
6448 }
6449 else if (err) {
6450 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6451 *err, srv->id);
6452 goto error;
6453 }
6454 if (delay <= 0) {
6455 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6456 delay, args[*cur_arg], srv->id);
6457 goto error;
6458 }
6459 srv->check.fastinter = delay;
6460
6461 out:
6462 return err_code;
6463
6464 error:
6465 err_code |= ERR_ALERT | ERR_FATAL;
6466 goto out;
6467}
6468
6469
6470/* Parse the "downinter" server keyword */
6471static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6472 char **errmsg)
6473{
6474 const char *err = NULL;
6475 unsigned int delay;
6476 int err_code = 0;
6477
6478 if (!*(args[*cur_arg+1])) {
6479 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6480 goto error;
6481 }
6482
6483 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6484 if (err == PARSE_TIME_OVER) {
6485 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6486 args[*cur_arg+1], args[*cur_arg], srv->id);
6487 goto error;
6488 }
6489 else if (err == PARSE_TIME_UNDER) {
6490 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6491 args[*cur_arg+1], args[*cur_arg], srv->id);
6492 goto error;
6493 }
6494 else if (err) {
6495 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6496 *err, srv->id);
6497 goto error;
6498 }
6499 if (delay <= 0) {
6500 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6501 delay, args[*cur_arg], srv->id);
6502 goto error;
6503 }
6504 srv->check.downinter = delay;
6505
6506 out:
6507 return err_code;
6508
6509 error:
6510 err_code |= ERR_ALERT | ERR_FATAL;
6511 goto out;
6512}
6513
6514/* Parse the "port" server keyword */
6515static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6516 char **errmsg)
6517{
6518 int err_code = 0;
6519
6520 if (!*(args[*cur_arg+1])) {
6521 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6522 goto error;
6523 }
6524
6525 global.maxsock++;
6526 srv->check.port = atol(args[*cur_arg+1]);
6527 srv->flags |= SRV_F_CHECKPORT;
6528
6529 out:
6530 return err_code;
6531
6532 error:
6533 err_code |= ERR_ALERT | ERR_FATAL;
6534 goto out;
6535}
6536
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006537static struct cfg_kw_list cfg_kws = {ILH, {
6538 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
6539 { 0, NULL, NULL },
6540}};
6541
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006542static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02006543 { "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 +02006544 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
6545 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
6546 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
6547 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
6548 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006549 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
6550 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
6551 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006552 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006553 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
6554 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
6555 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
6556 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
6557 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
6558 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
6559 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
6560 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006561 { NULL, NULL, 0 },
6562}};
6563
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006564INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006565INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006566
Willy Tarreaubd741542010-03-16 18:46:54 +01006567/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02006568 * Local variables:
6569 * c-indent-level: 8
6570 * c-basic-offset: 8
6571 * End:
6572 */