blob: 8ff6bc83cbba4503bc748f21628c2377f469d0c2 [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:
Christopher Fauletf930e4c2020-04-10 09:20:02 +0200657 chunk_appendf(chk, " (expect string '%.*s')", (unsigned int)istlen(expect->data), expect->data.ptr);
Gaetan Rivetb616add2020-02-07 15:37:17 +0100658 break;
659 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +0200660 chunk_appendf(chk, " (expect binary '%.*s')", (unsigned int)istlen(expect->data), expect->data.ptr);
Gaetan Rivetb616add2020-02-07 15:37:17 +0100661 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
Christopher Faulet95226db2020-04-15 11:34:04 +02001128/* returns the last NON-COMMENT tcp-check rule from list <list> or NULL if none
1129 * was found.
1130 */
1131static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
1132{
1133 struct tcpcheck_rule *r;
1134
1135 list_for_each_entry_rev(r, rules->list, list) {
1136 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1137 return r;
1138 }
1139 return NULL;
1140}
1141
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001142/* returns the NON-COMMENT tcp-check rule from list <list> following <start> or
1143 * NULL if non was found. If <start> is NULL, it relies on
1144 * get_first_tcpcheck_rule().
1145 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001146static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001147{
1148 struct tcpcheck_rule *r;
1149
1150 if (!start)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001151 return get_first_tcpcheck_rule(rules);
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001152
1153 r = LIST_NEXT(&start->list, typeof(r), list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001154 list_for_each_entry_from(r, rules->list, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01001155 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001156 return r;
1157 }
1158 return NULL;
1159}
1160
Willy Tarreau2e993902011-10-31 11:53:20 +01001161/*
Simon Horman98637e52014-06-20 12:30:16 +09001162 * establish a server health-check that makes use of a connection.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001163 *
1164 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001165 * - SF_ERR_NONE if everything's OK and tcpcheck_main() was not called
1166 * - SF_ERR_UP if if everything's OK and tcpcheck_main() was called
1167 * - SF_ERR_SRVTO if there are no more servers
1168 * - SF_ERR_SRVCL if the connection was refused by the server
1169 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1170 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1171 * - SF_ERR_INTERNAL for any other purely internal errors
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001172 * - 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 +01001173 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001174 * Note that we try to prevent the network stack from sending the ACK during the
1175 * connect() when a pure TCP check is used (without PROXY protocol).
1176 */
Simon Horman98637e52014-06-20 12:30:16 +09001177static int connect_conn_chk(struct task *t)
Simon Hormanb00d17a2014-06-13 16:18:16 +09001178{
1179 struct check *check = t->context;
1180 struct server *s = check->server;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001181 struct conn_stream *cs = check->cs;
1182 struct connection *conn = cs_conn(cs);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001183 struct protocol *proto;
1184 int ret;
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001185 int connflags = 0;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001186
Willy Tarreau00149122017-10-04 18:05:01 +02001187 /* we cannot have a connection here */
1188 if (conn)
1189 return SF_ERR_INTERNAL;
1190
Simon Hormanb00d17a2014-06-13 16:18:16 +09001191 /* prepare the check buffer.
1192 * This should not be used if check is the secondary agent check
1193 * of a server as s->proxy->check_req will relate to the
1194 * configuration of the primary check. Similarly, tcp-check uses
1195 * its own strings.
1196 */
1197 if (check->type && check->type != PR_O2_TCPCHK_CHK && !(check->state & CHK_ST_AGENT)) {
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001198 b_putblk(&check->bo, s->proxy->check_req, s->proxy->check_len);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001199
Christopher Faulet811f78c2020-04-01 11:10:27 +02001200 /* we want to check if this host replies to HTTP requests
Simon Hormanb00d17a2014-06-13 16:18:16 +09001201 * so we'll send the request, and won't wake the checker up now.
1202 */
Christopher Faulet811f78c2020-04-01 11:10:27 +02001203 if ((check->type) == PR_O2_HTTP_CHK) {
Cyril Bonté32602d22015-01-30 00:07:07 +01001204 /* prevent HTTP keep-alive when "http-check expect" is used */
1205 if (s->proxy->options2 & PR_O2_EXP_TYPE)
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001206 b_putist(&check->bo, ist("Connection: close\r\n"));
Christopher Faulet8acb1282020-04-09 08:44:06 +02001207
1208 /* If there is a body, add its content-length */
1209 if (s->proxy->check_body_len)
1210 chunk_appendf(&check->bo, "Content-Length: %s\r\n", ultoa(s->proxy->check_body_len));
1211
1212 /* Add configured headers */
1213 if (s->proxy->check_hdrs)
1214 b_putblk(&check->bo, s->proxy->check_hdrs, s->proxy->check_hdrs_len);
1215
1216 /* Add send-state header */
1217 if (s->proxy->options2 & PR_O2_CHK_SNDST)
1218 b_putblk(&check->bo, trash.area,
1219 httpchk_build_status_header(s, trash.area, trash.size));
1220
1221 /* end-of-header */
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001222 b_putist(&check->bo, ist("\r\n"));
Christopher Faulet8acb1282020-04-09 08:44:06 +02001223
1224 /* Add the body */
1225 if (s->proxy->check_body)
1226 b_putblk(&check->bo, s->proxy->check_body, s->proxy->check_body_len);
1227
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001228 *b_tail(&check->bo) = '\0'; /* to make gdb output easier to read */
Simon Hormanb00d17a2014-06-13 16:18:16 +09001229 }
1230 }
1231
Willy Tarreauf411cce2017-10-04 16:21:19 +02001232 /* for tcp-checks, the initial connection setup is handled separately as
1233 * it may be sent to a specific port and not to the server's.
1234 */
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001235 if (check->type == PR_O2_TCPCHK_CHK) {
1236 /* tcpcheck initialisation */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02001237 check->current_step = NULL;
Willy Tarreauf411cce2017-10-04 16:21:19 +02001238 tcpcheck_main(check);
1239 return SF_ERR_UP;
1240 }
1241
Simon Hormanb00d17a2014-06-13 16:18:16 +09001242 /* prepare a new connection */
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001243 cs = check->cs = cs_new(NULL);
1244 if (!check->cs)
Willy Tarreau00149122017-10-04 18:05:01 +02001245 return SF_ERR_RESOURCE;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001246 conn = cs->conn;
Olivier Houchard26e1a8f2018-09-12 15:15:12 +02001247 /* Maybe there were an older connection we were waiting on */
Willy Tarreau4f6516d2018-12-19 13:59:17 +01001248 check->wait_list.events = 0;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02001249 tasklet_set_tid(check->wait_list.tasklet, tid);
1250
Simon Hormanb00d17a2014-06-13 16:18:16 +09001251
Willy Tarreauca79f592019-07-17 19:04:47 +02001252 if (!sockaddr_alloc(&conn->dst))
1253 return SF_ERR_RESOURCE;
1254
Simon Horman41f58762015-01-30 11:22:56 +09001255 if (is_addr(&check->addr)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001256 /* we'll connect to the check addr specified on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001257 *conn->dst = check->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001258 }
1259 else {
1260 /* we'll connect to the addr on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001261 *conn->dst = s->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001262 }
1263
Alexander Liu2a54bb72019-05-22 19:44:48 +08001264 if (s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1265 conn->send_proxy_ofs = 1;
1266 conn->flags |= CO_FL_SOCKS4;
1267 }
1268
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001269 proto = protocol_by_family(conn->dst->ss_family);
Olivier Houchard6377a002017-12-01 22:04:05 +01001270 conn->target = &s->obj_type;
1271
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001272 if ((conn->dst->ss_family == AF_INET) || (conn->dst->ss_family == AF_INET6)) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001273 int i = 0;
1274
1275 i = srv_check_healthcheck_port(check);
Olivier Houchard6377a002017-12-01 22:04:05 +01001276 if (i == 0)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001277 return SF_ERR_CHK_PORT;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001278
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001279 set_host_port(conn->dst, i);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001280 }
1281
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001282 /* no client address */
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001283
Willy Tarreaube373152018-09-06 11:45:30 +02001284 conn_prepare(conn, proto, check->xprt);
Olivier Houchardf67be932019-01-29 15:47:43 +01001285 if (conn_install_mux(conn, &mux_pt_ops, cs, s->proxy, NULL) < 0)
1286 return SF_ERR_RESOURCE;
Willy Tarreaube373152018-09-06 11:45:30 +02001287 cs_attach(cs, check, &check_conn_cb);
1288
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001289 /* only plain tcp check supports quick ACK */
1290 connflags |= (check->type ? CONNECT_HAS_DATA : CONNECT_DELACK_ALWAYS);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001291
Willy Tarreaue7dff022015-04-03 01:14:29 +02001292 ret = SF_ERR_INTERNAL;
Olivier Houchardb68fda42017-08-04 18:39:01 +02001293 if (proto && proto->connect)
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001294 ret = proto->connect(conn, connflags);
Willy Tarreau16257f62017-11-02 15:45:00 +01001295
Willy Tarreau16257f62017-11-02 15:45:00 +01001296
Olivier Houchard9130a962017-10-17 17:33:43 +02001297#ifdef USE_OPENSSL
Olivier Houcharda48437b2019-01-29 16:37:52 +01001298 if (ret == SF_ERR_NONE) {
1299 if (s->check.sni)
1300 ssl_sock_set_servername(conn, s->check.sni);
1301 if (s->check.alpn_str)
1302 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str,
1303 s->check.alpn_len);
1304 }
Olivier Houchard9130a962017-10-17 17:33:43 +02001305#endif
Willy Tarreauf4949772017-05-06 08:45:28 +02001306 if (s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001307 conn->send_proxy_ofs = 1;
1308 conn->flags |= CO_FL_SEND_PROXY;
Olivier Houchard37d78972019-12-30 15:13:42 +01001309 }
1310 if (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4) &&
1311 conn_ctrl_ready(conn)) {
Olivier Houchardfe50bfb2019-05-27 12:09:19 +02001312 if (xprt_add_hs(conn) < 0)
1313 ret = SF_ERR_RESOURCE;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001314 }
1315
1316 return ret;
1317}
1318
Simon Horman98637e52014-06-20 12:30:16 +09001319static struct list pid_list = LIST_HEAD_INIT(pid_list);
Willy Tarreaubafbe012017-11-24 17:34:44 +01001320static struct pool_head *pool_head_pid_list;
Willy Tarreau86abe442018-11-25 20:12:18 +01001321__decl_spinlock(pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001322
1323void block_sigchld(void)
1324{
1325 sigset_t set;
1326 sigemptyset(&set);
1327 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001328 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001329}
1330
1331void unblock_sigchld(void)
1332{
1333 sigset_t set;
1334 sigemptyset(&set);
Willy Tarreauebc92442016-06-21 17:29:46 +02001335 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001336 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001337}
1338
Simon Horman98637e52014-06-20 12:30:16 +09001339static struct pid_list *pid_list_add(pid_t pid, struct task *t)
1340{
1341 struct pid_list *elem;
1342 struct check *check = t->context;
1343
Willy Tarreaubafbe012017-11-24 17:34:44 +01001344 elem = pool_alloc(pool_head_pid_list);
Simon Horman98637e52014-06-20 12:30:16 +09001345 if (!elem)
1346 return NULL;
1347 elem->pid = pid;
1348 elem->t = t;
1349 elem->exited = 0;
1350 check->curpid = elem;
1351 LIST_INIT(&elem->list);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001352
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_ADD(&pid_list, &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 return elem;
1358}
1359
Simon Horman98637e52014-06-20 12:30:16 +09001360static void pid_list_del(struct pid_list *elem)
1361{
1362 struct check *check;
1363
1364 if (!elem)
1365 return;
1366
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001367 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001368 LIST_DEL(&elem->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001369 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001370
Simon Horman98637e52014-06-20 12:30:16 +09001371 if (!elem->exited)
1372 kill(elem->pid, SIGTERM);
1373
1374 check = elem->t->context;
1375 check->curpid = NULL;
Willy Tarreaubafbe012017-11-24 17:34:44 +01001376 pool_free(pool_head_pid_list, elem);
Simon Horman98637e52014-06-20 12:30:16 +09001377}
1378
1379/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
1380static void pid_list_expire(pid_t pid, int status)
1381{
1382 struct pid_list *elem;
1383
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001384 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001385 list_for_each_entry(elem, &pid_list, list) {
1386 if (elem->pid == pid) {
1387 elem->t->expire = now_ms;
1388 elem->status = status;
1389 elem->exited = 1;
Cyril Bonté9dbcfab2014-08-07 01:55:39 +02001390 task_wakeup(elem->t, TASK_WOKEN_IO);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001391 break;
Simon Horman98637e52014-06-20 12:30:16 +09001392 }
1393 }
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001394 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001395}
1396
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001397static void sigchld_handler(struct sig_handler *sh)
Simon Horman98637e52014-06-20 12:30:16 +09001398{
1399 pid_t pid;
1400 int status;
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001401
Simon Horman98637e52014-06-20 12:30:16 +09001402 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
1403 pid_list_expire(pid, status);
1404}
1405
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001406static int init_pid_list(void)
1407{
Willy Tarreaubafbe012017-11-24 17:34:44 +01001408 if (pool_head_pid_list != NULL)
Simon Horman98637e52014-06-20 12:30:16 +09001409 /* Nothing to do */
1410 return 0;
1411
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001412 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001413 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
1414 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001415 return 1;
1416 }
1417
Willy Tarreaubafbe012017-11-24 17:34:44 +01001418 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
1419 if (pool_head_pid_list == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001420 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
1421 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001422 return 1;
1423 }
1424
1425 return 0;
1426}
1427
Cyril Bontéac92a062014-12-27 22:28:38 +01001428/* helper macro to set an environment variable and jump to a specific label on failure. */
1429#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001430
1431/*
Cyril Bontéac92a062014-12-27 22:28:38 +01001432 * helper function to allocate enough memory to store an environment variable.
1433 * It will also check that the environment variable is updatable, and silently
1434 * fail if not.
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001435 */
Cyril Bontéac92a062014-12-27 22:28:38 +01001436static int extchk_setenv(struct check *check, int idx, const char *value)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001437{
1438 int len, ret;
Cyril Bontéac92a062014-12-27 22:28:38 +01001439 char *envname;
1440 int vmaxlen;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001441
Cyril Bontéac92a062014-12-27 22:28:38 +01001442 if (idx < 0 || idx >= EXTCHK_SIZE) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001443 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
Cyril Bontéac92a062014-12-27 22:28:38 +01001444 return 1;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001445 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001446
1447 envname = extcheck_envs[idx].name;
1448 vmaxlen = extcheck_envs[idx].vmaxlen;
1449
1450 /* Check if the environment variable is already set, and silently reject
1451 * the update if this one is not updatable. */
1452 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
1453 return 0;
1454
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001455 /* Instead of sending NOT_USED, sending an empty value is preferable */
1456 if (strcmp(value, "NOT_USED") == 0) {
1457 value = "";
1458 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001459
1460 len = strlen(envname) + 1;
1461 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
1462 len += strlen(value);
1463 else
1464 len += vmaxlen;
1465
1466 if (!check->envp[idx])
1467 check->envp[idx] = malloc(len + 1);
1468
1469 if (!check->envp[idx]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001470 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001471 return 1;
1472 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001473 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001474 if (ret < 0) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001475 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001476 return 1;
1477 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001478 else if (ret > len) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001479 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001480 return 1;
1481 }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001482 return 0;
1483}
Simon Horman98637e52014-06-20 12:30:16 +09001484
1485static int prepare_external_check(struct check *check)
1486{
1487 struct server *s = check->server;
1488 struct proxy *px = s->proxy;
1489 struct listener *listener = NULL, *l;
1490 int i;
Simon Horman98637e52014-06-20 12:30:16 +09001491 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001492 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001493
1494 list_for_each_entry(l, &px->conf.listeners, by_fe)
1495 /* Use the first INET, INET6 or UNIX listener */
1496 if (l->addr.ss_family == AF_INET ||
1497 l->addr.ss_family == AF_INET6 ||
1498 l->addr.ss_family == AF_UNIX) {
1499 listener = l;
1500 break;
1501 }
1502
Simon Horman98637e52014-06-20 12:30:16 +09001503 check->curpid = NULL;
Cyril Bontéac92a062014-12-27 22:28:38 +01001504 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
1505 if (!check->envp) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001506 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
Cyril Bontéac92a062014-12-27 22:28:38 +01001507 goto err;
1508 }
Simon Horman98637e52014-06-20 12:30:16 +09001509
Cyril Bontéac92a062014-12-27 22:28:38 +01001510 check->argv = calloc(6, sizeof(char *));
1511 if (!check->argv) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001512 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001513 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001514 }
Simon Horman98637e52014-06-20 12:30:16 +09001515
1516 check->argv[0] = px->check_command;
1517
Cyril Bonté777be862014-12-02 21:21:35 +01001518 if (!listener) {
1519 check->argv[1] = strdup("NOT_USED");
1520 check->argv[2] = strdup("NOT_USED");
1521 }
1522 else if (listener->addr.ss_family == AF_INET ||
Simon Horman98637e52014-06-20 12:30:16 +09001523 listener->addr.ss_family == AF_INET6) {
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001524 addr_to_str(&listener->addr, buf, sizeof(buf));
1525 check->argv[1] = strdup(buf);
1526 port_to_str(&listener->addr, buf, sizeof(buf));
1527 check->argv[2] = strdup(buf);
Cyril Bonté777be862014-12-02 21:21:35 +01001528 }
1529 else if (listener->addr.ss_family == AF_UNIX) {
Simon Horman98637e52014-06-20 12:30:16 +09001530 const struct sockaddr_un *un;
1531
1532 un = (struct sockaddr_un *)&listener->addr;
1533 check->argv[1] = strdup(un->sun_path);
1534 check->argv[2] = strdup("NOT_USED");
Cyril Bonté777be862014-12-02 21:21:35 +01001535 }
1536 else {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001537 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001538 goto err;
1539 }
1540
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001541 if (!check->argv[1] || !check->argv[2]) {
1542 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1543 goto err;
1544 }
1545
1546 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
1547 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
1548 if (!check->argv[3] || !check->argv[4]) {
1549 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1550 goto err;
1551 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001552
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001553 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
Willy Tarreau04276f32017-01-06 17:41:29 +01001554 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001555 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Simon Horman98637e52014-06-20 12:30:16 +09001556
Cyril Bontéac92a062014-12-27 22:28:38 +01001557 for (i = 0; i < 5; i++) {
1558 if (!check->argv[i]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001559 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001560 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001561 }
1562 }
Simon Horman98637e52014-06-20 12:30:16 +09001563
Cyril Bontéac92a062014-12-27 22:28:38 +01001564 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001565 /* Add proxy environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001566 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
1567 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
1568 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
1569 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001570 /* Add server environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001571 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
1572 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
1573 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
1574 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
1575 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
1576 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
1577
1578 /* Ensure that we don't leave any hole in check->envp */
1579 for (i = 0; i < EXTCHK_SIZE; i++)
1580 if (!check->envp[i])
1581 EXTCHK_SETENV(check, i, "", err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001582
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001583 return 1;
Simon Horman98637e52014-06-20 12:30:16 +09001584err:
1585 if (check->envp) {
Cyril Bontéac92a062014-12-27 22:28:38 +01001586 for (i = 0; i < EXTCHK_SIZE; i++)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001587 free(check->envp[i]);
Simon Horman98637e52014-06-20 12:30:16 +09001588 free(check->envp);
1589 check->envp = NULL;
1590 }
1591
1592 if (check->argv) {
1593 for (i = 1; i < 5; i++)
1594 free(check->argv[i]);
1595 free(check->argv);
1596 check->argv = NULL;
1597 }
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001598 return 0;
Simon Horman98637e52014-06-20 12:30:16 +09001599}
1600
Simon Hormanb00d17a2014-06-13 16:18:16 +09001601/*
Simon Horman98637e52014-06-20 12:30:16 +09001602 * establish a server health-check that makes use of a process.
1603 *
1604 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001605 * - SF_ERR_NONE if everything's OK
Willy Tarreaue7dff022015-04-03 01:14:29 +02001606 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
Tim Düsterhus4896c442016-11-29 02:15:19 +01001607 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Horman98637e52014-06-20 12:30:16 +09001608 *
1609 * Blocks and then unblocks SIGCHLD
1610 */
1611static int connect_proc_chk(struct task *t)
1612{
Cyril Bontéac92a062014-12-27 22:28:38 +01001613 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001614 struct check *check = t->context;
1615 struct server *s = check->server;
1616 struct proxy *px = s->proxy;
1617 int status;
1618 pid_t pid;
1619
Willy Tarreaue7dff022015-04-03 01:14:29 +02001620 status = SF_ERR_RESOURCE;
Simon Horman98637e52014-06-20 12:30:16 +09001621
1622 block_sigchld();
1623
1624 pid = fork();
1625 if (pid < 0) {
Willy Tarreaud96f1122019-12-03 07:07:36 +01001626 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
1627 (global.tune.options & GTUNE_INSECURE_FORK) ?
1628 "" : " (likely caused by missing 'insecure-fork-wanted')",
Christopher Faulet767a84b2017-11-24 16:50:31 +01001629 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001630 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1631 goto out;
1632 }
1633 if (pid == 0) {
1634 /* Child */
1635 extern char **environ;
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001636 struct rlimit limit;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001637 int fd;
1638
1639 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
1640 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
1641
Willy Tarreau2555ccf2019-02-21 22:22:06 +01001642 my_closefrom(fd);
Willy Tarreaub7b24782016-06-21 15:32:29 +02001643
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001644 /* restore the initial FD limits */
1645 limit.rlim_cur = rlim_fd_cur_at_boot;
1646 limit.rlim_max = rlim_fd_max_at_boot;
1647 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
1648 getrlimit(RLIMIT_NOFILE, &limit);
1649 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
1650 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
1651 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
1652 }
1653
Simon Horman98637e52014-06-20 12:30:16 +09001654 environ = check->envp;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001655
1656 /* Update some environment variables and command args: curconn, server addr and server port */
Cyril Bontéac92a062014-12-27 22:28:38 +01001657 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001658
1659 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
1660 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
1661
1662 *check->argv[4] = 0;
1663 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1664 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
1665 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
1666
Willy Tarreau2df8cad2019-07-01 07:51:29 +02001667 haproxy_unblock_signals();
Simon Horman98637e52014-06-20 12:30:16 +09001668 execvp(px->check_command, check->argv);
Christopher Faulet767a84b2017-11-24 16:50:31 +01001669 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
1670 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001671 exit(-1);
1672 }
1673
1674 /* Parent */
1675 if (check->result == CHK_RES_UNKNOWN) {
1676 if (pid_list_add(pid, t) != NULL) {
1677 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1678
1679 if (px->timeout.check && px->timeout.connect) {
1680 int t_con = tick_add(now_ms, px->timeout.connect);
1681 t->expire = tick_first(t->expire, t_con);
1682 }
Willy Tarreaue7dff022015-04-03 01:14:29 +02001683 status = SF_ERR_NONE;
Simon Horman98637e52014-06-20 12:30:16 +09001684 goto out;
1685 }
1686 else {
1687 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1688 }
1689 kill(pid, SIGTERM); /* process creation error */
1690 }
1691 else
1692 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1693
1694out:
1695 unblock_sigchld();
1696 return status;
1697}
1698
1699/*
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001700 * manages a server health-check that uses an external process. Returns
Willy Tarreaubaaee002006-06-26 02:48:02 +02001701 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001702 *
1703 * Please do NOT place any return statement in this function and only leave
1704 * via the out_unlock label.
Willy Tarreaubaaee002006-06-26 02:48:02 +02001705 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001706static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09001707{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001708 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09001709 struct server *s = check->server;
Simon Horman98637e52014-06-20 12:30:16 +09001710 int rv;
1711 int ret;
1712 int expired = tick_is_expired(t->expire, now_ms);
1713
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001714 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09001715 if (!(check->state & CHK_ST_INPROGRESS)) {
1716 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001717 if (!expired) /* woke up too early */
1718 goto out_unlock;
Simon Horman98637e52014-06-20 12:30:16 +09001719
1720 /* we don't send any health-checks when the proxy is
1721 * stopped, the server should not be checked or the check
1722 * is disabled.
1723 */
1724 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
1725 s->proxy->state == PR_STSTOPPED)
1726 goto reschedule;
1727
1728 /* we'll initiate a new check */
1729 set_server_check_status(check, HCHK_STATUS_START, NULL);
1730
1731 check->state |= CHK_ST_INPROGRESS;
1732
Simon Hormandbf70192015-01-30 11:22:53 +09001733 ret = connect_proc_chk(t);
Willy Tarreaud7c3fbd2017-10-04 15:19:26 +02001734 if (ret == SF_ERR_NONE) {
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001735 /* the process was forked, we allow up to min(inter,
1736 * timeout.connect) for it to report its status, but
1737 * only when timeout.check is set as it may be to short
1738 * for a full check otherwise.
Simon Horman98637e52014-06-20 12:30:16 +09001739 */
1740 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1741
1742 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
1743 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
1744 t->expire = tick_first(t->expire, t_con);
1745 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02001746 task_set_affinity(t, tid_bit);
Simon Horman98637e52014-06-20 12:30:16 +09001747 goto reschedule;
Simon Horman98637e52014-06-20 12:30:16 +09001748 }
1749
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001750 /* here, we failed to start the check */
Simon Horman98637e52014-06-20 12:30:16 +09001751
1752 check->state &= ~CHK_ST_INPROGRESS;
1753 check_notify_failure(check);
1754
1755 /* we allow up to min(inter, timeout.connect) for a connection
1756 * to establish but only when timeout.check is set
1757 * as it may be to short for a full check otherwise
1758 */
1759 while (tick_is_expired(t->expire, now_ms)) {
1760 int t_con;
1761
1762 t_con = tick_add(t->expire, s->proxy->timeout.connect);
1763 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
1764
1765 if (s->proxy->timeout.check)
1766 t->expire = tick_first(t->expire, t_con);
1767 }
1768 }
1769 else {
1770 /* there was a test running.
1771 * First, let's check whether there was an uncaught error,
1772 * which can happen on connect timeout or error.
1773 */
1774 if (check->result == CHK_RES_UNKNOWN) {
1775 /* good connection is enough for pure TCP check */
1776 struct pid_list *elem = check->curpid;
1777 int status = HCHK_STATUS_UNKNOWN;
1778
1779 if (elem->exited) {
1780 status = elem->status; /* Save in case the process exits between use below */
1781 if (!WIFEXITED(status))
1782 check->code = -1;
1783 else
1784 check->code = WEXITSTATUS(status);
1785 if (!WIFEXITED(status) || WEXITSTATUS(status))
1786 status = HCHK_STATUS_PROCERR;
1787 else
1788 status = HCHK_STATUS_PROCOK;
1789 } else if (expired) {
1790 status = HCHK_STATUS_PROCTOUT;
Christopher Faulet767a84b2017-11-24 16:50:31 +01001791 ha_warning("kill %d\n", (int)elem->pid);
Simon Horman98637e52014-06-20 12:30:16 +09001792 kill(elem->pid, SIGTERM);
1793 }
1794 set_server_check_status(check, status, NULL);
1795 }
1796
1797 if (check->result == CHK_RES_FAILED) {
1798 /* a failure or timeout detected */
1799 check_notify_failure(check);
1800 }
1801 else if (check->result == CHK_RES_CONDPASS) {
1802 /* check is OK but asks for stopping mode */
1803 check_notify_stopping(check);
1804 }
1805 else if (check->result == CHK_RES_PASSED) {
1806 /* a success was detected */
1807 check_notify_success(check);
1808 }
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001809 task_set_affinity(t, 1);
Simon Horman98637e52014-06-20 12:30:16 +09001810 check->state &= ~CHK_ST_INPROGRESS;
1811
1812 pid_list_del(check->curpid);
1813
1814 rv = 0;
1815 if (global.spread_checks > 0) {
1816 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01001817 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Simon Horman98637e52014-06-20 12:30:16 +09001818 }
1819 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
1820 }
1821
1822 reschedule:
1823 while (tick_is_expired(t->expire, now_ms))
1824 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001825
1826 out_unlock:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001827 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09001828 return t;
1829}
1830
1831/*
1832 * manages a server health-check that uses a connection. Returns
1833 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001834 *
1835 * Please do NOT place any return statement in this function and only leave
1836 * via the out_unlock label.
Simon Horman98637e52014-06-20 12:30:16 +09001837 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001838static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001839{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001840 struct check *check = context;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001841 struct proxy *proxy = check->proxy;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001842 struct conn_stream *cs = check->cs;
1843 struct connection *conn = cs_conn(cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001844 int rv;
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001845 int ret;
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01001846 int expired = tick_is_expired(t->expire, now_ms);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001847
Olivier Houchard0923fa42019-01-11 18:43:04 +01001848 if (check->server)
1849 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau2c115e52013-12-11 19:41:16 +01001850 if (!(check->state & CHK_ST_INPROGRESS)) {
Willy Tarreau5a78f362012-11-23 12:47:05 +01001851 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001852 if (!expired) /* woke up too early */
1853 goto out_unlock;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001854
Simon Horman671b6f02013-11-25 10:46:39 +09001855 /* we don't send any health-checks when the proxy is
1856 * stopped, the server should not be checked or the check
1857 * is disabled.
Willy Tarreaubaaee002006-06-26 02:48:02 +02001858 */
Willy Tarreau0d924cc2013-12-11 21:26:24 +01001859 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001860 proxy->state == PR_STSTOPPED)
Willy Tarreau5a78f362012-11-23 12:47:05 +01001861 goto reschedule;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001862
1863 /* we'll initiate a new check */
Simon Horman4a741432013-02-23 15:35:38 +09001864 set_server_check_status(check, HCHK_STATUS_START, NULL);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001865
Willy Tarreau2c115e52013-12-11 19:41:16 +01001866 check->state |= CHK_ST_INPROGRESS;
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001867 b_reset(&check->bi);
1868 b_reset(&check->bo);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001869
Olivier Houchardaebeff72019-11-29 16:18:51 +01001870 task_set_affinity(t, tid_bit);
Simon Hormandbf70192015-01-30 11:22:53 +09001871 ret = connect_conn_chk(t);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001872 cs = check->cs;
1873 conn = cs_conn(cs);
Willy Tarreau00149122017-10-04 18:05:01 +02001874
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001875 switch (ret) {
Willy Tarreaue7dff022015-04-03 01:14:29 +02001876 case SF_ERR_UP:
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001877 goto out_unlock;
1878
Willy Tarreaue7dff022015-04-03 01:14:29 +02001879 case SF_ERR_NONE:
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001880 /* we allow up to min(inter, timeout.connect) for a connection
1881 * to establish but only when timeout.check is set
1882 * as it may be to short for a full check otherwise
1883 */
Simon Horman4a741432013-02-23 15:35:38 +09001884 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001885 if (proxy->timeout.check && proxy->timeout.connect) {
1886 int t_con = tick_add(now_ms, proxy->timeout.connect);
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001887 t->expire = tick_first(t->expire, t_con);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001888 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01001889
Willy Tarreaucc705a62019-09-05 17:51:30 +02001890 if (check->type) {
1891 /* send the request if we have one. We avoid receiving
1892 * if not connected, unless we didn't subscribe for
1893 * sending since otherwise we won't be woken up.
1894 */
1895 __event_srv_chk_w(cs);
Willy Tarreau911db9b2020-01-23 16:27:54 +01001896 if (!(conn->flags & CO_FL_WAIT_XPRT) ||
Willy Tarreauc5940392019-09-05 17:38:40 +02001897 !(check->wait_list.events & SUB_RETRY_SEND))
1898 __event_srv_chk_r(cs);
Willy Tarreaucc705a62019-09-05 17:51:30 +02001899 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01001900
Willy Tarreau5a78f362012-11-23 12:47:05 +01001901 goto reschedule;
1902
Willy Tarreaue7dff022015-04-03 01:14:29 +02001903 case SF_ERR_SRVTO: /* ETIMEDOUT */
1904 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
Willy Tarreau00149122017-10-04 18:05:01 +02001905 if (conn)
1906 conn->flags |= CO_FL_ERROR;
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001907 chk_report_conn_err(check, errno, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01001908 break;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001909 /* should share same code than cases below */
1910 case SF_ERR_CHK_PORT:
1911 check->state |= CHK_ST_PORT_MISS;
Willy Tarreaue7dff022015-04-03 01:14:29 +02001912 case SF_ERR_PRXCOND:
1913 case SF_ERR_RESOURCE:
1914 case SF_ERR_INTERNAL:
Willy Tarreau00149122017-10-04 18:05:01 +02001915 if (conn)
1916 conn->flags |= CO_FL_ERROR;
1917 chk_report_conn_err(check, conn ? 0 : ENOMEM, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01001918 break;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001919 }
1920
Willy Tarreau5a78f362012-11-23 12:47:05 +01001921 /* here, we have seen a synchronous error, no fd was allocated */
Olivier Houchardaebeff72019-11-29 16:18:51 +01001922 task_set_affinity(t, MAX_THREADS_MASK);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001923 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02001924 if (check->wait_list.events)
1925 cs->conn->xprt->unsubscribe(cs->conn,
1926 cs->conn->xprt_ctx,
1927 check->wait_list.events,
1928 &check->wait_list);
1929 /* We may have been scheduled to run, and the
1930 * I/O handler expects to have a cs, so remove
1931 * the tasklet
1932 */
Willy Tarreau86eded62019-06-14 14:47:49 +02001933 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001934 cs_destroy(cs);
1935 cs = check->cs = NULL;
1936 conn = NULL;
Olivier Houchard390485a2017-10-24 19:03:30 +02001937 }
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001938
Willy Tarreau2c115e52013-12-11 19:41:16 +01001939 check->state &= ~CHK_ST_INPROGRESS;
Willy Tarreau4eec5472014-05-20 22:32:27 +02001940 check_notify_failure(check);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001941
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001942 /* we allow up to min(inter, timeout.connect) for a connection
1943 * to establish but only when timeout.check is set
1944 * as it may be to short for a full check otherwise
1945 */
Willy Tarreau0c303ee2008-07-07 00:09:58 +02001946 while (tick_is_expired(t->expire, now_ms)) {
1947 int t_con;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001948
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001949 t_con = tick_add(t->expire, proxy->timeout.connect);
Simon Horman4a741432013-02-23 15:35:38 +09001950 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001951 if (proxy->timeout.check)
Willy Tarreau0c303ee2008-07-07 00:09:58 +02001952 t->expire = tick_first(t->expire, t_con);
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001953 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02001954 }
1955 else {
Willy Tarreauf1503172012-09-28 19:39:36 +02001956 /* there was a test running.
1957 * First, let's check whether there was an uncaught error,
1958 * which can happen on connect timeout or error.
1959 */
Simon Hormanccaabcd2014-06-20 12:29:47 +09001960 if (check->result == CHK_RES_UNKNOWN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001961 /* good connection is enough for pure TCP check */
Willy Tarreau911db9b2020-01-23 16:27:54 +01001962 if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Christopher Fauletf61f33a2020-03-27 18:55:49 +01001963 if (check->use_ssl == 1)
Simon Horman4a741432013-02-23 15:35:38 +09001964 set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
Willy Tarreauf1503172012-09-28 19:39:36 +02001965 else
Simon Horman4a741432013-02-23 15:35:38 +09001966 set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01001967 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +02001968 else if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001969 chk_report_conn_err(check, 0, expired);
Willy Tarreauf1503172012-09-28 19:39:36 +02001970 }
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01001971 else
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001972 goto out_unlock; /* timeout not reached, wait again */
Willy Tarreauf1503172012-09-28 19:39:36 +02001973 }
1974
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01001975 /* check complete or aborted */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001976
1977 check->current_step = NULL;
1978 if (check->sess != NULL) {
1979 session_free(check->sess);
1980 check->sess = NULL;
1981 }
1982
Willy Tarreau00149122017-10-04 18:05:01 +02001983 if (conn && conn->xprt) {
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001984 /* The check was aborted and the connection was not yet closed.
1985 * This can happen upon timeout, or when an external event such
1986 * as a failed response coupled with "observe layer7" caused the
1987 * server state to be suddenly changed.
1988 */
Willy Tarreaud85c4852015-03-13 00:40:28 +01001989 conn_sock_drain(conn);
Willy Tarreaua553ae92017-10-05 18:52:17 +02001990 cs_close(cs);
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001991 }
1992
Willy Tarreauac59f362017-10-08 11:10:19 +02001993 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02001994 if (check->wait_list.events)
1995 cs->conn->xprt->unsubscribe(cs->conn,
1996 cs->conn->xprt_ctx,
1997 check->wait_list.events,
1998 &check->wait_list);
1999 /* We may have been scheduled to run, and the
Willy Tarreau86eded62019-06-14 14:47:49 +02002000 * I/O handler expects to have a cs, so remove
2001 * the tasklet
2002 */
2003 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02002004 cs_destroy(cs);
2005 cs = check->cs = NULL;
2006 conn = NULL;
Willy Tarreau00149122017-10-04 18:05:01 +02002007 }
2008
Olivier Houchard0923fa42019-01-11 18:43:04 +01002009 if (check->server) {
2010 if (check->result == CHK_RES_FAILED) {
2011 /* a failure or timeout detected */
2012 check_notify_failure(check);
2013 }
2014 else if (check->result == CHK_RES_CONDPASS) {
2015 /* check is OK but asks for stopping mode */
2016 check_notify_stopping(check);
2017 }
2018 else if (check->result == CHK_RES_PASSED) {
2019 /* a success was detected */
2020 check_notify_success(check);
2021 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02002022 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02002023 task_set_affinity(t, MAX_THREADS_MASK);
Willy Tarreau2c115e52013-12-11 19:41:16 +01002024 check->state &= ~CHK_ST_INPROGRESS;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002025
Olivier Houchard0923fa42019-01-11 18:43:04 +01002026 if (check->server) {
2027 rv = 0;
2028 if (global.spread_checks > 0) {
2029 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01002030 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Olivier Houchard0923fa42019-01-11 18:43:04 +01002031 }
2032 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Willy Tarreaubaaee002006-06-26 02:48:02 +02002033 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02002034 }
Willy Tarreau5a78f362012-11-23 12:47:05 +01002035
2036 reschedule:
2037 while (tick_is_expired(t->expire, now_ms))
Simon Horman4a741432013-02-23 15:35:38 +09002038 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01002039 out_unlock:
Olivier Houchard0923fa42019-01-11 18:43:04 +01002040 if (check->server)
2041 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau26c25062009-03-08 09:38:41 +01002042 return t;
Willy Tarreaubaaee002006-06-26 02:48:02 +02002043}
2044
Simon Horman98637e52014-06-20 12:30:16 +09002045/*
2046 * manages a server health-check. Returns
2047 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
2048 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02002049static struct task *process_chk(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09002050{
Olivier Houchard9f6af332018-05-25 14:04:04 +02002051 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09002052
2053 if (check->type == PR_O2_EXT_CHK)
Olivier Houchard9f6af332018-05-25 14:04:04 +02002054 return process_chk_proc(t, context, state);
2055 return process_chk_conn(t, context, state);
Baptiste Assmanna68ca962015-04-14 01:15:08 +02002056
Simon Horman98637e52014-06-20 12:30:16 +09002057}
2058
Simon Horman5c942422013-11-25 10:46:32 +09002059static int start_check_task(struct check *check, int mininter,
2060 int nbcheck, int srvpos)
2061{
2062 struct task *t;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002063 unsigned long thread_mask = MAX_THREADS_MASK;
2064
2065 if (check->type == PR_O2_EXT_CHK)
2066 thread_mask = 1;
2067
Simon Horman5c942422013-11-25 10:46:32 +09002068 /* task for the check */
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002069 if ((t = task_new(thread_mask)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002070 ha_alert("Starting [%s:%s] check: out of memory.\n",
2071 check->server->proxy->id, check->server->id);
Simon Horman5c942422013-11-25 10:46:32 +09002072 return 0;
2073 }
2074
2075 check->task = t;
2076 t->process = process_chk;
2077 t->context = check;
2078
Willy Tarreau1746eec2014-04-25 10:46:47 +02002079 if (mininter < srv_getinter(check))
2080 mininter = srv_getinter(check);
2081
2082 if (global.max_spread_checks && mininter > global.max_spread_checks)
2083 mininter = global.max_spread_checks;
2084
Simon Horman5c942422013-11-25 10:46:32 +09002085 /* check this every ms */
Willy Tarreau1746eec2014-04-25 10:46:47 +02002086 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
Simon Horman5c942422013-11-25 10:46:32 +09002087 check->start = now;
2088 task_queue(t);
2089
2090 return 1;
2091}
2092
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002093/*
2094 * Start health-check.
Willy Tarreau865c5142016-12-21 20:04:48 +01002095 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002096 */
Willy Tarreau865c5142016-12-21 20:04:48 +01002097static int start_checks()
2098{
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002099
2100 struct proxy *px;
2101 struct server *s;
2102 struct task *t;
Simon Horman4a741432013-02-23 15:35:38 +09002103 int nbcheck=0, mininter=0, srvpos=0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002104
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002105 /* 0- init the dummy frontend used to create all checks sessions */
2106 init_new_proxy(&checks_fe);
2107 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
2108 checks_fe.mode = PR_MODE_TCP;
2109 checks_fe.maxconn = 0;
2110 checks_fe.conn_retries = CONN_RETRIES;
2111 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
2112 checks_fe.timeout.client = TICK_ETERNITY;
2113
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002114 /* 1- count the checkers to run simultaneously.
2115 * We also determine the minimum interval among all of those which
2116 * have an interval larger than SRV_CHK_INTER_THRES. This interval
2117 * will be used to spread their start-up date. Those which have
Jamie Gloudon801a0a32012-08-25 00:18:33 -04002118 * a shorter interval will start independently and will not dictate
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002119 * too short an interval for all others.
2120 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002121 for (px = proxies_list; px; px = px->next) {
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002122 for (s = px->srv; s; s = s->next) {
Willy Tarreaue7b73482013-11-21 11:50:50 +01002123 if (s->slowstart) {
Emeric Brunc60def82017-09-27 14:59:38 +02002124 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002125 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002126 return ERR_ALERT | ERR_FATAL;
Willy Tarreaue7b73482013-11-21 11:50:50 +01002127 }
2128 /* We need a warmup task that will be called when the server
2129 * state switches from down to up.
2130 */
2131 s->warmup = t;
2132 t->process = server_warmup;
2133 t->context = s;
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002134 /* server can be in this state only because of */
Emeric Brun52a91d32017-08-31 14:41:55 +02002135 if (s->next_state == SRV_ST_STARTING)
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002136 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 +01002137 }
2138
Willy Tarreaud8514a22013-12-11 21:10:14 +01002139 if (s->check.state & CHK_ST_CONFIGURED) {
2140 nbcheck++;
2141 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
2142 (!mininter || mininter > srv_getinter(&s->check)))
2143 mininter = srv_getinter(&s->check);
2144 }
Willy Tarreau15f39102013-12-11 20:41:18 +01002145
Willy Tarreaud8514a22013-12-11 21:10:14 +01002146 if (s->agent.state & CHK_ST_CONFIGURED) {
2147 nbcheck++;
2148 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
2149 (!mininter || mininter > srv_getinter(&s->agent)))
2150 mininter = srv_getinter(&s->agent);
2151 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002152 }
2153 }
2154
Simon Horman4a741432013-02-23 15:35:38 +09002155 if (!nbcheck)
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002156 return 0;
2157
2158 srand((unsigned)time(NULL));
2159
2160 /*
2161 * 2- start them as far as possible from each others. For this, we will
2162 * start them after their interval set to the min interval divided by
2163 * the number of servers, weighted by the server's position in the list.
2164 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002165 for (px = proxies_list; px; px = px->next) {
Simon Horman98637e52014-06-20 12:30:16 +09002166 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
2167 if (init_pid_list()) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002168 ha_alert("Starting [%s] check: out of memory.\n", px->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002169 return ERR_ALERT | ERR_FATAL;
Simon Horman98637e52014-06-20 12:30:16 +09002170 }
2171 }
2172
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002173 for (s = px->srv; s; s = s->next) {
Simon Hormand60d6912013-11-25 10:46:36 +09002174 /* A task for the main check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002175 if (s->check.state & CHK_ST_CONFIGURED) {
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002176 if (s->check.type == PR_O2_EXT_CHK) {
2177 if (!prepare_external_check(&s->check))
Willy Tarreau865c5142016-12-21 20:04:48 +01002178 return ERR_ALERT | ERR_FATAL;
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002179 }
Simon Hormand60d6912013-11-25 10:46:36 +09002180 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
Willy Tarreau865c5142016-12-21 20:04:48 +01002181 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002182 srvpos++;
2183 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002184
Simon Hormand60d6912013-11-25 10:46:36 +09002185 /* A task for a auxiliary agent check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002186 if (s->agent.state & CHK_ST_CONFIGURED) {
Simon Hormand60d6912013-11-25 10:46:36 +09002187 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
Willy Tarreau865c5142016-12-21 20:04:48 +01002188 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002189 }
2190 srvpos++;
2191 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002192 }
2193 }
2194 return 0;
2195}
Willy Tarreaubaaee002006-06-26 02:48:02 +02002196
2197/*
Willy Tarreau5b3a2022012-09-28 15:01:02 +02002198 * Perform content verification check on data in s->check.buffer buffer.
Willy Tarreaubd741542010-03-16 18:46:54 +01002199 * The buffer MUST be terminated by a null byte before calling this function.
2200 * Sets server status appropriately. The caller is responsible for ensuring
2201 * that the buffer contains at least 13 characters. If <done> is zero, we may
2202 * return 0 to indicate that data is required to decide of a match.
2203 */
2204static int httpchk_expect(struct server *s, int done)
2205{
Christopher Faulet1bc04c72017-10-29 20:14:08 +01002206 static THREAD_LOCAL char status_msg[] = "HTTP status check returned code <000>";
Willy Tarreaubd741542010-03-16 18:46:54 +01002207 char status_code[] = "000";
2208 char *contentptr;
2209 int crlf;
2210 int ret;
2211
2212 switch (s->proxy->options2 & PR_O2_EXP_TYPE) {
2213 case PR_O2_EXP_STS:
2214 case PR_O2_EXP_RSTS:
Willy Tarreauc9fa0482018-07-10 17:43:27 +02002215 memcpy(status_code, b_head(&s->check.bi) + 9, 3);
2216 memcpy(status_msg + strlen(status_msg) - 4, b_head(&s->check.bi) + 9, 3);
Willy Tarreaubd741542010-03-16 18:46:54 +01002217
2218 if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STS)
2219 ret = strncmp(s->proxy->expect_str, status_code, 3) == 0;
2220 else
Thierry FOURNIER09af0d62014-06-18 11:35:54 +02002221 ret = regex_exec(s->proxy->expect_regex, status_code);
Willy Tarreaubd741542010-03-16 18:46:54 +01002222
2223 /* we necessarily have the response, so there are no partial failures */
2224 if (s->proxy->options2 & PR_O2_EXP_INV)
2225 ret = !ret;
2226
Simon Horman4a741432013-02-23 15:35:38 +09002227 set_server_check_status(&s->check, ret ? HCHK_STATUS_L7OKD : HCHK_STATUS_L7STS, status_msg);
Willy Tarreaubd741542010-03-16 18:46:54 +01002228 break;
2229
2230 case PR_O2_EXP_STR:
2231 case PR_O2_EXP_RSTR:
2232 /* very simple response parser: ignore CR and only count consecutive LFs,
2233 * stop with contentptr pointing to first char after the double CRLF or
2234 * to '\0' if crlf < 2.
2235 */
2236 crlf = 0;
Willy Tarreauc9fa0482018-07-10 17:43:27 +02002237 for (contentptr = b_head(&s->check.bi); *contentptr; contentptr++) {
Willy Tarreaubd741542010-03-16 18:46:54 +01002238 if (crlf >= 2)
2239 break;
2240 if (*contentptr == '\r')
2241 continue;
2242 else if (*contentptr == '\n')
2243 crlf++;
2244 else
2245 crlf = 0;
2246 }
2247
2248 /* Check that response contains a body... */
2249 if (crlf < 2) {
2250 if (!done)
2251 return 0;
2252
Simon Horman4a741432013-02-23 15:35:38 +09002253 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002254 "HTTP content check could not find a response body");
2255 return 1;
2256 }
2257
2258 /* Check that response body is not empty... */
2259 if (*contentptr == '\0') {
Willy Tarreaua164fb52011-04-13 09:32:41 +02002260 if (!done)
2261 return 0;
2262
Simon Horman4a741432013-02-23 15:35:38 +09002263 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002264 "HTTP content check found empty response body");
2265 return 1;
2266 }
2267
2268 /* Check the response content against the supplied string
2269 * or regex... */
2270 if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STR)
2271 ret = strstr(contentptr, s->proxy->expect_str) != NULL;
2272 else
Thierry FOURNIER09af0d62014-06-18 11:35:54 +02002273 ret = regex_exec(s->proxy->expect_regex, contentptr);
Willy Tarreaubd741542010-03-16 18:46:54 +01002274
2275 /* if we don't match, we may need to wait more */
2276 if (!ret && !done)
2277 return 0;
2278
2279 if (ret) {
2280 /* content matched */
2281 if (s->proxy->options2 & PR_O2_EXP_INV)
Simon Horman4a741432013-02-23 15:35:38 +09002282 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002283 "HTTP check matched unwanted content");
2284 else
Simon Horman4a741432013-02-23 15:35:38 +09002285 set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
Willy Tarreaubd741542010-03-16 18:46:54 +01002286 "HTTP content check matched");
2287 }
2288 else {
2289 if (s->proxy->options2 & PR_O2_EXP_INV)
Simon Horman4a741432013-02-23 15:35:38 +09002290 set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
Willy Tarreaubd741542010-03-16 18:46:54 +01002291 "HTTP check did not match unwanted content");
2292 else
Simon Horman4a741432013-02-23 15:35:38 +09002293 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002294 "HTTP content check did not match");
2295 }
2296 break;
2297 }
2298 return 1;
2299}
2300
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002301/*
2302 * return the id of a step in a send/expect session
2303 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002304static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002305{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002306 if (!rule)
2307 rule = check->current_step;
Willy Tarreau213c6782014-10-02 14:51:02 +02002308
Christopher Faulet3c29aa62020-03-24 13:31:19 +01002309 /* no last started step => first step */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002310 if (!rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002311 return 1;
2312
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002313 /* last step is the first implicit connect */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002314 if (rule->index == 0 &&
2315 rule->action == TCPCHK_ACT_CONNECT &&
Christopher Fauletbb591a12020-04-01 16:52:17 +02002316 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002317 return 0;
2318
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002319 return rule->index + 1;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002320}
2321
Christopher Faulet206368d2020-04-03 14:51:06 +02002322static void tcpcheck_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2323 int match, struct ist info)
2324{
2325 struct sample *smp;
2326
2327 if (istlen(info)) {
2328 chunk_strncat(msg, info.ptr, info.len);
2329 goto comment;
2330 }
2331 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
2332 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
2333 goto comment;
2334 }
2335
Christopher Faulet799f3a42020-04-07 12:06:14 +02002336 if (check->type == PR_O2_TCPCHK_CHK && (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK))
2337 goto comment;
2338
Christopher Faulet206368d2020-04-03 14:51:06 +02002339 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
2340 switch (rule->expect.type) {
2341 case TCPCHK_EXPECT_STRING:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02002342 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), rule->expect.data.ptr,
2343 tcpcheck_get_step_id(check, rule));
Christopher Faulet206368d2020-04-03 14:51:06 +02002344 break;
2345 case TCPCHK_EXPECT_BINARY:
2346 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
2347 break;
2348 case TCPCHK_EXPECT_REGEX:
2349 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
2350 break;
2351 case TCPCHK_EXPECT_REGEX_BINARY:
2352 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
2353
2354 /* If references to the matched text were made, divide the
2355 * offsets by 2 to match offset of the original response buffer.
2356 */
Christopher Faulet12d57402020-04-10 09:58:42 +02002357 if (rule->expect.flags & TCPCHK_EXPT_FL_CAP) {
Christopher Faulet206368d2020-04-03 14:51:06 +02002358 int i;
2359
2360 for (i = 1; i < MAX_MATCH && pmatch[i].rm_so != -1; i++) {
2361 pmatch[i].rm_so /= 2; /* at first matched char. */
2362 pmatch[i].rm_eo /= 2; /* at last matched char. */
2363 }
2364 }
2365 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02002366 case TCPCHK_EXPECT_CUSTOM:
2367 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
2368 break;
Christopher Faulet206368d2020-04-03 14:51:06 +02002369 case TCPCHK_EXPECT_UNDEF:
2370 /* Should never happen. */
2371 return;
2372 }
2373
2374 comment:
2375 if (rule->comment) {
2376 chunk_strcat(msg, " comment: ");
Christopher Faulet12d57402020-04-10 09:58:42 +02002377 if (rule->expect.flags & TCPCHK_EXPT_FL_CAP) {
Christopher Faulet206368d2020-04-03 14:51:06 +02002378 int ret = exp_replace(b_tail(msg), b_room(msg), b_head(&check->bi), rule->comment, pmatch);
2379 if (ret != -1) /* ignore comment if too large */
2380 msg->data += ret;
2381 }
2382 else
2383 chunk_strcat(msg, rule->comment);
2384 }
2385
2386 if (rule->expect.status_expr) {
2387 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2388 rule->expect.status_expr, SMP_T_SINT);
2389 if (smp)
2390 check->code = smp->data.u.sint;
2391 }
2392
2393 *(b_tail(msg)) = '\0';
2394}
2395
2396static void tcpcheck_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2397 struct ist info)
2398{
2399 struct sample *smp;
2400
2401 if (istlen(info))
2402 chunk_strncat(msg, info.ptr, info.len);
2403 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
2404 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
2405 &rule->expect.onsuccess_fmt);
Christopher Faulet799f3a42020-04-07 12:06:14 +02002406 else if (check->type == PR_O2_TCPCHK_CHK && !(check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK))
Christopher Faulet206368d2020-04-03 14:51:06 +02002407 chunk_strcat(msg, "(tcp-check)");
2408
2409 if (rule->expect.status_expr) {
2410 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2411 rule->expect.status_expr, SMP_T_SINT);
2412 if (smp)
2413 check->code = smp->data.u.sint;
2414 }
2415
2416 *(b_tail(msg)) = '\0';
2417}
2418
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002419static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
2420 unsigned int offset, int last_read)
2421{
2422 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2423 enum healthcheck_status status;
2424 struct buffer *msg = NULL;
2425 struct ist desc = ist(NULL);
2426 unsigned int err = 0, plen = 0;
2427
2428
2429 /* 3 Bytes for the packet length and 1 byte for the sequence id */
2430 if (!last_read && b_data(&check->bi) < offset+4) {
2431 if (!last_read)
2432 goto wait_more_data;
2433
2434 /* invalid length or truncated response */
2435 status = HCHK_STATUS_L7RSP;
2436 goto error;
2437 }
2438
2439 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
2440 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
2441 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
2442
2443 if (b_data(&check->bi) < offset+plen+4) {
2444 if (!last_read)
2445 goto wait_more_data;
2446
2447 /* invalid length or truncated response */
2448 status = HCHK_STATUS_L7RSP;
2449 goto error;
2450 }
2451
2452 if (*b_peek(&check->bi, offset+4) == '\xff') {
2453 /* MySQL Error packet always begin with field_count = 0xff */
2454 status = HCHK_STATUS_L7STS;
2455 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
2456 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
2457 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
2458 goto error;
2459 }
2460
2461 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
2462 /* Not the last rule, continue */
2463 goto out;
2464 }
2465
2466 /* We set the MySQL Version in description for information purpose
2467 * FIXME : it can be cool to use MySQL Version for other purpose,
2468 * like mark as down old MySQL server.
2469 */
Christopher Fauletec07e382020-04-07 14:56:26 +02002470 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002471
2472 out:
2473 free_trash_chunk(msg);
2474 return ret;
2475
2476 error:
2477 ret = TCPCHK_EVAL_STOP;
2478 check->code = err;
2479 msg = alloc_trash_chunk();
2480 if (msg)
2481 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2482 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2483 goto out;
2484
2485 wait_more_data:
2486 ret = TCPCHK_EVAL_WAIT;
2487 goto out;
2488}
2489
2490
2491static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
2492{
2493 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
2494}
2495
2496static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
2497{
2498 unsigned int hslen = 0;
2499
2500 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
2501 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
2502 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
2503
2504 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
2505}
2506
Christopher Faulet1997eca2020-04-03 23:13:50 +02002507static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
2508{
2509 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2510 enum healthcheck_status status;
2511 struct buffer *msg = NULL;
2512 struct ist desc = ist(NULL);
2513 unsigned short msglen = 0;
2514
2515 /* Check if the server speaks LDAP (ASN.1/BER)
2516 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
2517 * http://tools.ietf.org/html/rfc4511
2518 */
2519 /* size of LDAPMessage */
2520 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
2521
2522 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
2523 * messageID: 0x02 0x01 0x01: INTEGER 1
2524 * protocolOp: 0x61: bindResponse
2525 */
2526 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
2527 status = HCHK_STATUS_L7RSP;
2528 desc = ist("Not LDAPv3 protocol");
2529 goto error;
2530 }
2531
2532 /* size of bindResponse */
2533 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
2534
2535 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2536 * ldapResult: 0x0a 0x01: ENUMERATION
2537 */
2538 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
2539 status = HCHK_STATUS_L7RSP;
2540 desc = ist("Not LDAPv3 protocol");
2541 goto error;
2542 }
2543
2544 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2545 * resultCode
2546 */
2547 check->code = *(b_head(&check->bi) + msglen + 9);
2548 if (check->code) {
2549 status = HCHK_STATUS_L7STS;
2550 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
2551 goto error;
2552 }
2553
Christopher Fauletec07e382020-04-07 14:56:26 +02002554 set_server_check_status(check, rule->expect.ok_status, "Success");
Christopher Faulet1997eca2020-04-03 23:13:50 +02002555
2556 out:
2557 free_trash_chunk(msg);
2558 return ret;
2559
2560 error:
2561 ret = TCPCHK_EVAL_STOP;
2562 msg = alloc_trash_chunk();
2563 if (msg)
2564 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2565 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2566 goto out;
2567
2568 wait_more_data:
2569 ret = TCPCHK_EVAL_WAIT;
2570 goto out;
2571}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002572
Christopher Faulet267b01b2020-04-04 10:27:09 +02002573
2574static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
2575{
2576 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2577 enum healthcheck_status status;
2578 struct buffer *msg = NULL;
2579 struct ist desc = ist(NULL);
2580 unsigned int framesz;
2581
2582
2583 memcpy(&framesz, b_head(&check->bi), 4);
2584 framesz = ntohl(framesz);
2585
2586 if (!last_read && b_data(&check->bi) < (4+framesz))
2587 goto wait_more_data;
2588
2589 memset(b_orig(&trash), 0, b_size(&trash));
2590 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
2591 status = HCHK_STATUS_L7RSP;
2592 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
2593 goto error;
2594 }
2595
Christopher Fauletec07e382020-04-07 14:56:26 +02002596 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Christopher Faulet267b01b2020-04-04 10:27:09 +02002597
2598 out:
2599 free_trash_chunk(msg);
2600 return ret;
2601
2602 error:
2603 ret = TCPCHK_EVAL_STOP;
2604 msg = alloc_trash_chunk();
2605 if (msg)
2606 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2607 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2608 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002609
2610 wait_more_data:
2611 ret = TCPCHK_EVAL_WAIT;
2612 goto out;
2613}
2614
2615static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
2616{
2617 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
2618 enum healthcheck_status status = HCHK_STATUS_CHECKED;
2619 const char *hs = NULL; /* health status */
2620 const char *as = NULL; /* admin status */
2621 const char *ps = NULL; /* performance status */
2622 const char *cs = NULL; /* maxconn */
2623 const char *err = NULL; /* first error to report */
2624 const char *wrn = NULL; /* first warning to report */
2625 char *cmd, *p;
2626
2627 /* We're getting an agent check response. The agent could
2628 * have been disabled in the mean time with a long check
2629 * still pending. It is important that we ignore the whole
2630 * response.
2631 */
2632 if (!(check->state & CHK_ST_ENABLED))
2633 goto out;
2634
2635 /* The agent supports strings made of a single line ended by the
2636 * first CR ('\r') or LF ('\n'). This line is composed of words
2637 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
2638 * line may optionally contained a description of a state change
2639 * after a sharp ('#'), which is only considered if a health state
2640 * is announced.
2641 *
2642 * Words may be composed of :
2643 * - a numeric weight suffixed by the percent character ('%').
2644 * - a health status among "up", "down", "stopped", and "fail".
2645 * - an admin status among "ready", "drain", "maint".
2646 *
2647 * These words may appear in any order. If multiple words of the
2648 * same category appear, the last one wins.
2649 */
2650
2651 p = b_head(&check->bi);
2652 while (*p && *p != '\n' && *p != '\r')
2653 p++;
2654
2655 if (!*p) {
2656 if (!last_read)
2657 goto wait_more_data;
2658
2659 /* at least inform the admin that the agent is mis-behaving */
2660 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
2661 goto out;
2662 }
2663
2664 *p = 0;
2665 cmd = b_head(&check->bi);
2666
2667 while (*cmd) {
2668 /* look for next word */
2669 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
2670 cmd++;
2671 continue;
2672 }
2673
2674 if (*cmd == '#') {
2675 /* this is the beginning of a health status description,
2676 * skip the sharp and blanks.
2677 */
2678 cmd++;
2679 while (*cmd == '\t' || *cmd == ' ')
2680 cmd++;
2681 break;
2682 }
2683
2684 /* find the end of the word so that we have a null-terminated
2685 * word between <cmd> and <p>.
2686 */
2687 p = cmd + 1;
2688 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
2689 p++;
2690 if (*p)
2691 *p++ = 0;
2692
2693 /* first, health statuses */
2694 if (strcasecmp(cmd, "up") == 0) {
2695 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
2696 status = HCHK_STATUS_L7OKD;
2697 hs = cmd;
2698 }
2699 else if (strcasecmp(cmd, "down") == 0) {
2700 check->server->check.health = 0;
2701 status = HCHK_STATUS_L7STS;
2702 hs = cmd;
2703 }
2704 else if (strcasecmp(cmd, "stopped") == 0) {
2705 check->server->check.health = 0;
2706 status = HCHK_STATUS_L7STS;
2707 hs = cmd;
2708 }
2709 else if (strcasecmp(cmd, "fail") == 0) {
2710 check->server->check.health = 0;
2711 status = HCHK_STATUS_L7STS;
2712 hs = cmd;
2713 }
2714 /* admin statuses */
2715 else if (strcasecmp(cmd, "ready") == 0) {
2716 as = cmd;
2717 }
2718 else if (strcasecmp(cmd, "drain") == 0) {
2719 as = cmd;
2720 }
2721 else if (strcasecmp(cmd, "maint") == 0) {
2722 as = cmd;
2723 }
2724 /* try to parse a weight here and keep the last one */
2725 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
2726 ps = cmd;
2727 }
2728 /* try to parse a maxconn here */
2729 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
2730 cs = cmd;
2731 }
2732 else {
2733 /* keep a copy of the first error */
2734 if (!err)
2735 err = cmd;
2736 }
2737 /* skip to next word */
2738 cmd = p;
2739 }
2740 /* here, cmd points either to \0 or to the beginning of a
2741 * description. Skip possible leading spaces.
2742 */
2743 while (*cmd == ' ' || *cmd == '\n')
2744 cmd++;
2745
2746 /* First, update the admin status so that we avoid sending other
2747 * possibly useless warnings and can also update the health if
2748 * present after going back up.
2749 */
2750 if (as) {
2751 if (strcasecmp(as, "drain") == 0)
2752 srv_adm_set_drain(check->server);
2753 else if (strcasecmp(as, "maint") == 0)
2754 srv_adm_set_maint(check->server);
2755 else
2756 srv_adm_set_ready(check->server);
2757 }
2758
2759 /* now change weights */
2760 if (ps) {
2761 const char *msg;
2762
2763 msg = server_parse_weight_change_request(check->server, ps);
2764 if (!wrn || !*wrn)
2765 wrn = msg;
2766 }
2767
2768 if (cs) {
2769 const char *msg;
2770
2771 cs += strlen("maxconn:");
2772
2773 msg = server_parse_maxconn_change_request(check->server, cs);
2774 if (!wrn || !*wrn)
2775 wrn = msg;
2776 }
2777
2778 /* and finally health status */
2779 if (hs) {
2780 /* We'll report some of the warnings and errors we have
2781 * here. Down reports are critical, we leave them untouched.
2782 * Lack of report, or report of 'UP' leaves the room for
2783 * ERR first, then WARN.
2784 */
2785 const char *msg = cmd;
2786 struct buffer *t;
2787
2788 if (!*msg || status == HCHK_STATUS_L7OKD) {
2789 if (err && *err)
2790 msg = err;
2791 else if (wrn && *wrn)
2792 msg = wrn;
2793 }
2794
2795 t = get_trash_chunk();
2796 chunk_printf(t, "via agent : %s%s%s%s",
2797 hs, *msg ? " (" : "",
2798 msg, *msg ? ")" : "");
2799 set_server_check_status(check, status, t->area);
2800 }
2801 else if (err && *err) {
2802 /* No status change but we'd like to report something odd.
2803 * Just report the current state and copy the message.
2804 */
2805 chunk_printf(&trash, "agent reports an error : %s", err);
2806 set_server_check_status(check, status/*check->status*/, trash.area);
2807 }
2808 else if (wrn && *wrn) {
2809 /* No status change but we'd like to report something odd.
2810 * Just report the current state and copy the message.
2811 */
2812 chunk_printf(&trash, "agent warns : %s", wrn);
2813 set_server_check_status(check, status/*check->status*/, trash.area);
2814 }
2815 else
2816 set_server_check_status(check, status, NULL);
2817
2818 out:
2819 return ret;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002820
2821 wait_more_data:
2822 ret = TCPCHK_EVAL_WAIT;
2823 goto out;
2824}
2825
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002826/* Evaluate a TCPCHK_ACT_CONNECT rule. It returns 1 to evaluate the next rule, 0
2827 * to wait and -1 to stop the check. */
2828static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002829{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002830 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2831 struct tcpcheck_connect *connect = &rule->connect;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01002832 struct proxy *proxy = check->proxy;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002833 struct server *s = check->server;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002834 struct task *t = check->task;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002835 struct conn_stream *cs;
2836 struct connection *conn = NULL;
2837 struct protocol *proto;
2838 struct xprt_ops *xprt;
Christopher Faulet5c288742020-03-31 08:15:58 +02002839 int status, port;
Willy Tarreauef953952014-10-02 14:30:14 +02002840
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002841 /* For a connect action we'll create a new connection. We may also have
2842 * to kill a previous one. But we don't want to leave *without* a
2843 * connection if we came here from the connection layer, hence with a
2844 * connection. Thus we'll proceed in the following order :
2845 * 1: close but not release previous connection (handled by the caller)
2846 * 2: try to get a new connection
2847 * 3: release and replace the old one on success
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002848 */
Willy Tarreau449f9522015-05-13 15:39:48 +02002849
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002850 /* 2- prepare new connection */
2851 cs = cs_new(NULL);
2852 if (!cs) {
2853 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
2854 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002855 if (rule->comment)
2856 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002857 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2858 ret = TCPCHK_EVAL_STOP;
Christopher Fauletb6102852017-11-28 10:06:29 +01002859 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002860 }
2861
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002862 /* 3- release and replace the old one on success */
2863 if (check->cs) {
2864 if (check->wait_list.events)
2865 cs->conn->xprt->unsubscribe(cs->conn, cs->conn->xprt_ctx,
2866 check->wait_list.events, &check->wait_list);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002867
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002868 /* We may have been scheduled to run, and the I/O handler
2869 * expects to have a cs, so remove the tasklet
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002870 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002871 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
2872 cs_destroy(check->cs);
2873 }
Willy Tarreaudeccd112018-06-14 18:38:55 +02002874
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002875 tasklet_set_tid(check->wait_list.tasklet, tid);
Willy Tarreauabca5b62013-12-06 14:19:25 +01002876
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002877 check->cs = cs;
2878 conn = cs->conn;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002879
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002880 /* Maybe there were an older connection we were waiting on */
2881 check->wait_list.events = 0;
2882 conn->target = s ? &s->obj_type : &proxy->obj_type;
Willy Tarreauf3d34822014-12-08 12:11:28 +01002883
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002884 /* no client address */
2885 if (!sockaddr_alloc(&conn->dst)) {
2886 status = SF_ERR_RESOURCE;
2887 goto fail_check;
2888 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002889
Christopher Faulet5c288742020-03-31 08:15:58 +02002890 /* connect to the connect rule addr if specified, otherwise the check
2891 * addr if specified on the server. otherwise, use the server addr
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002892 */
Christopher Faulet5c288742020-03-31 08:15:58 +02002893 *conn->dst = (is_addr(&connect->addr)
2894 ? connect->addr
2895 : (is_addr(&check->addr) ? check->addr : s->addr));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002896 proto = protocol_by_family(conn->dst->ss_family);
Willy Tarreau00149122017-10-04 18:05:01 +02002897
Christopher Faulet5c288742020-03-31 08:15:58 +02002898 port = 0;
2899 if (!port && connect->port)
2900 port = connect->port;
Christopher Fauletb7d30092020-03-30 15:19:03 +02002901 if (!port && connect->port_expr) {
2902 struct sample *smp;
2903
2904 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
2905 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
2906 connect->port_expr, SMP_T_SINT);
2907 if (smp)
2908 port = smp->data.u.sint;
2909 }
Christopher Faulet5c288742020-03-31 08:15:58 +02002910 if (!port && is_inet_addr(&connect->addr))
2911 port = get_host_port(&connect->addr);
2912 if (!port && check->port)
2913 port = check->port;
2914 if (!port && is_inet_addr(&check->addr))
2915 port = get_host_port(&check->addr);
2916 if (!port)
2917 port = s->svc_port;
2918 set_host_port(conn->dst, port);
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002919
Christopher Fauletbb591a12020-04-01 16:52:17 +02002920 xprt = ((connect->options & TCPCHK_OPT_SSL)
2921 ? xprt_get(XPRT_SSL)
2922 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Willy Tarreau00149122017-10-04 18:05:01 +02002923
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002924 conn_prepare(conn, proto, xprt);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002925 if (conn_install_mux(conn, &mux_pt_ops, cs, proxy, check->sess) < 0) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002926 status = SF_ERR_RESOURCE;
2927 goto fail_check;
2928 }
2929 cs_attach(cs, check, &check_conn_cb);
Willy Tarreau00149122017-10-04 18:05:01 +02002930
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002931 status = SF_ERR_INTERNAL;
2932 if (proto && proto->connect) {
2933 struct tcpcheck_rule *next;
2934 int flags = CONNECT_HAS_DATA;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02002935
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002936 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
2937 if (!next || next->action != TCPCHK_ACT_EXPECT)
2938 flags |= CONNECT_DELACK_ALWAYS;
2939 status = proto->connect(conn, flags);
2940 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002941
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002942#ifdef USE_OPENSSL
Christopher Fauletbb591a12020-04-01 16:52:17 +02002943 if (status == SF_ERR_NONE) {
2944 if (connect->sni)
2945 ssl_sock_set_servername(conn, connect->sni);
2946 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
2947 ssl_sock_set_servername(conn, s->check.sni);
2948
2949 if (connect->alpn)
2950 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
2951 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
2952 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002953 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02002954#endif
Christopher Fauletbb591a12020-04-01 16:52:17 +02002955 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
2956 conn->send_proxy_ofs = 1;
2957 conn->flags |= CO_FL_SOCKS4;
2958 }
2959 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
2960 conn->send_proxy_ofs = 1;
2961 conn->flags |= CO_FL_SOCKS4;
2962 }
2963
2964 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
2965 conn->send_proxy_ofs = 1;
2966 conn->flags |= CO_FL_SEND_PROXY;
2967 }
2968 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
2969 conn->send_proxy_ofs = 1;
2970 conn->flags |= CO_FL_SEND_PROXY;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002971 }
Willy Tarreauca79f592019-07-17 19:04:47 +02002972
Christopher Fauletbb591a12020-04-01 16:52:17 +02002973 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
2974 /* Some servers don't like reset on close */
2975 fdtab[cs->conn->handle.fd].linger_risk = 0;
2976 }
2977
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002978 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
2979 if (xprt_add_hs(conn) < 0)
2980 status = SF_ERR_RESOURCE;
2981 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002982
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002983 fail_check:
2984 /* It can return one of :
2985 * - SF_ERR_NONE if everything's OK
2986 * - SF_ERR_SRVTO if there are no more servers
2987 * - SF_ERR_SRVCL if the connection was refused by the server
2988 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
2989 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2990 * - SF_ERR_INTERNAL for any other purely internal errors
2991 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2992 * Note that we try to prevent the network stack from sending the ACK during the
2993 * connect() when a pure TCP check is used (without PROXY protocol).
2994 */
2995 switch (status) {
2996 case SF_ERR_NONE:
2997 /* we allow up to min(inter, timeout.connect) for a connection
2998 * to establish but only when timeout.check is set as it may be
2999 * to short for a full check otherwise
3000 */
3001 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003002
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003003 if (proxy->timeout.check && proxy->timeout.connect) {
3004 int t_con = tick_add(now_ms, proxy->timeout.connect);
3005 t->expire = tick_first(t->expire, t_con);
3006 }
3007 break;
3008 case SF_ERR_SRVTO: /* ETIMEDOUT */
3009 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
3010 chunk_printf(&trash, "TCPCHK error establishing connection at step %d: %s",
3011 tcpcheck_get_step_id(check, rule), strerror(errno));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003012 if (rule->comment)
3013 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003014 set_server_check_status(check, HCHK_STATUS_L4CON, trash.area);
3015 ret = TCPCHK_EVAL_STOP;
3016 goto out;
3017 case SF_ERR_PRXCOND:
3018 case SF_ERR_RESOURCE:
3019 case SF_ERR_INTERNAL:
3020 chunk_printf(&trash, "TCPCHK error establishing connection at step %d",
3021 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003022 if (rule->comment)
3023 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003024 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3025 ret = TCPCHK_EVAL_STOP;
3026 goto out;
3027 }
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003028
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003029 /* don't do anything until the connection is established */
3030 if (conn->flags & CO_FL_WAIT_XPRT) {
3031 ret = TCPCHK_EVAL_WAIT;
3032 goto out;
3033 }
Willy Tarreaube373152018-09-06 11:45:30 +02003034
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003035 out:
3036 if (conn && check->result == CHK_RES_FAILED)
3037 conn->flags |= CO_FL_ERROR;
3038 return ret;
3039}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02003040
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003041/* Evaluate a TCPCHK_ACT_SEND rule. It returns 1 to evaluate the next rule, 0
3042 * to wait and -1 to stop the check. */
3043static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
3044{
3045 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3046 struct tcpcheck_send *send = &rule->send;
3047 struct conn_stream *cs = check->cs;
3048 struct connection *conn = cs_conn(cs);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003049 struct buffer *tmp = NULL;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003050
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003051 /* reset the read & write buffer */
3052 b_reset(&check->bi);
3053 b_reset(&check->bo);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01003054
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003055 switch (send->type) {
3056 case TCPCHK_SEND_STRING:
3057 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003058 if (istlen(send->data) >= b_size(&check->bo)) {
3059 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
3060 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
3061 tcpcheck_get_step_id(check, rule));
3062 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3063 ret = TCPCHK_EVAL_STOP;
3064 goto out;
3065 }
3066 b_putist(&check->bo, send->data);
3067 break;
3068 case TCPCHK_SEND_STRING_LF:
3069 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
3070 if (!b_data(&check->bo))
3071 goto out;
3072 break;
3073 case TCPCHK_SEND_BINARY_LF:
3074 tmp = alloc_trash_chunk();
3075 if (!tmp)
3076 goto error_lf;
3077 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
3078 if (!b_data(tmp))
3079 goto out;
3080 tmp->area[tmp->data] = '\0';
3081 b_set_data(&check->bo, b_size(&check->bo));
3082 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
3083 goto error_lf;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003084 break;
3085 case TCPCHK_SEND_UNDEF:
3086 /* Should never happen. */
3087 ret = TCPCHK_EVAL_STOP;
3088 goto out;
3089 };
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003090
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003091 if (conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0) <= 0) {
3092 ret = TCPCHK_EVAL_WAIT;
3093 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))
3094 ret = TCPCHK_EVAL_STOP;
3095 goto out;
3096 }
3097 if (b_data(&check->bo)) {
3098 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3099 ret = TCPCHK_EVAL_WAIT;
3100 goto out;
3101 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003102
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003103 out:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003104 free_trash_chunk(tmp);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003105 return ret;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003106
3107 error_lf:
3108 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
3109 tcpcheck_get_step_id(check, rule));
3110 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3111 ret = TCPCHK_EVAL_STOP;
3112 goto out;
3113
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003114}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01003115
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003116/* Evaluate a TCPCHK_ACT_EXPECT rule. It returns 1 to evaluate the next rule, 0
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003117 * to wait and -1 to stop the check.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003118 */
3119static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
3120{
3121 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Fauletec07e382020-04-07 14:56:26 +02003122 struct tcpcheck_expect *expect = &rule->expect;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003123 struct buffer *msg = NULL;
Christopher Faulet12d57402020-04-10 09:58:42 +02003124 int match, inverse;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003125
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003126 /* The current expect might need more data than the previous one, check again
3127 * that the minimum amount data required to match is respected.
3128 */
3129 if (!last_read) {
3130 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003131 (b_data(&check->bi) < istlen(expect->data))) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003132 ret = TCPCHK_EVAL_WAIT;
3133 goto out;
3134 }
3135 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
3136 ret = TCPCHK_EVAL_WAIT;
3137 goto out;
3138 }
3139 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003140
Christopher Faulet12d57402020-04-10 09:58:42 +02003141 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003142 /* Make GCC happy ; initialize match to a failure state. */
Christopher Faulet12d57402020-04-10 09:58:42 +02003143 match = inverse;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003144
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003145 switch (expect->type) {
3146 case TCPCHK_EXPECT_STRING:
3147 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003148 match = my_memmem(b_head(&check->bi), b_data(&check->bi), expect->data.ptr, istlen(expect->data)) != NULL;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003149 break;
3150 case TCPCHK_EXPECT_REGEX:
Christopher Faulet12d57402020-04-10 09:58:42 +02003151 if (expect->flags & TCPCHK_EXPT_FL_CAP)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003152 match = regex_exec_match2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1),
3153 MAX_MATCH, pmatch, 0);
3154 else
3155 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
3156 break;
Willy Tarreau7df8ca62019-07-15 10:57:51 +02003157
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003158 case TCPCHK_EXPECT_REGEX_BINARY:
3159 chunk_reset(&trash);
3160 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet12d57402020-04-10 09:58:42 +02003161 if (expect->flags & TCPCHK_EXPT_FL_CAP)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003162 match = regex_exec_match2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1),
3163 MAX_MATCH, pmatch, 0);
3164 else
3165 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
3166 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003167 case TCPCHK_EXPECT_CUSTOM:
3168 if (expect->custom)
3169 ret = expect->custom(check, rule, last_read);
3170 goto out;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003171 case TCPCHK_EXPECT_UNDEF:
3172 /* Should never happen. */
3173 ret = TCPCHK_EVAL_STOP;
3174 goto out;
3175 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003176
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003177
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003178 /* Wait for more data on mismatch only if no minimum is defined (-1),
3179 * otherwise the absence of match is already conclusive.
3180 */
3181 if (!match && !last_read && (expect->min_recv == -1)) {
3182 ret = TCPCHK_EVAL_WAIT;
3183 goto out;
3184 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003185
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003186 /* Result as expected, next rule. */
Christopher Faulet12d57402020-04-10 09:58:42 +02003187 if (match ^ inverse)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003188 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003189
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01003190
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003191 /* From this point on, we matched something we did not want, this is an error state. */
3192 ret = TCPCHK_EVAL_STOP;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003193 msg = alloc_trash_chunk();
Christopher Faulet206368d2020-04-03 14:51:06 +02003194 if (msg)
3195 tcpcheck_onerror_message(msg, check, rule, match, ist(NULL));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003196 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
Christopher Faulet206368d2020-04-03 14:51:06 +02003197 free_trash_chunk(msg);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003198 ret = TCPCHK_EVAL_STOP;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003199
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003200 out:
3201 return ret;
3202}
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003203
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003204/* Evaluate a TCPCHK_ACT_ACTION_KW rule. It returns 1 to evaluate the next rule, 0
3205 * to wait and -1 to stop the check.
3206 */
3207static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
3208{
3209 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3210 struct act_rule *act_rule;
3211 enum act_return act_ret;
3212
3213 act_rule =rule->action_kw.rule;
3214 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
3215 if (act_ret != ACT_RET_CONT) {
3216 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
3217 tcpcheck_get_step_id(check, rule));
3218 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3219 ret = TCPCHK_EVAL_STOP;
3220 }
3221
3222 return ret;
3223}
3224
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003225/* proceed with next steps for the TCP checks <check>. Note that this is called
3226 * both from the connection's wake() callback and from the check scheduling task.
3227 * It returns 0 on normal cases, or <0 if a close() has happened on an existing
3228 * connection, presenting the risk of an fd replacement.
3229 *
3230 * Please do NOT place any return statement in this function and only leave
3231 * via the out_end_tcpcheck label after setting retcode.
3232 */
3233static int tcpcheck_main(struct check *check)
3234{
3235 struct tcpcheck_rule *rule;
3236 struct conn_stream *cs = check->cs;
3237 struct connection *conn = cs_conn(cs);
3238 int must_read = 1, last_read = 0;
3239 int ret, retcode = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003240
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003241 /* here, we know that the check is complete or that it failed */
3242 if (check->result != CHK_RES_UNKNOWN)
3243 goto out_end_tcpcheck;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003244
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003245 /* 1- check for connection error, if any */
3246 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3247 goto out_end_tcpcheck;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003248
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003249 /* 2- check if we are waiting for the connection establishment. It only
3250 * happens during TCPCHK_ACT_CONNECT. */
3251 if (conn && (conn->flags & CO_FL_WAIT_XPRT))
3252 goto out;
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003253
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003254 /* 3- check for pending outgoing data. It only happens during TCPCHK_ACT_SEND. */
3255 if (conn && b_data(&check->bo)) {
3256 ret = conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
3257 if (ret <= 0) {
3258 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3259 goto out_end_tcpcheck;
3260 goto out;
3261 }
3262 if (b_data(&check->bo)) {
3263 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3264 goto out;
3265 }
3266 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003267
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003268 /* Now evaluate the tcp-check rules */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003269
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003270 /* If check->current_step is defined, we are in resume condition. For
3271 * TCPCHK_ACT_CONNECT and TCPCHK_ACT_SEND rules, we must go to the next
3272 * rule before resuming the evaluation. For TCPCHK_ACT_EXPECT, we
3273 * re-evaluate the current rule. Others cannot yield.
3274 */
3275 if (check->current_step) {
3276 if (check->current_step->action == TCPCHK_ACT_CONNECT ||
3277 check->current_step->action == TCPCHK_ACT_SEND)
3278 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
3279 else
3280 rule = check->current_step;
3281 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003282 else {
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003283 struct tcpcheck_var *var;
3284
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003285 /* First evaluation, create a session */
Gaetan Rivet13a50432020-02-21 18:13:44 +01003286 check->sess = session_new(&checks_fe, NULL, (check->server ? &check->server->obj_type : NULL));
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003287 if (!check->sess) {
3288 chunk_printf(&trash, "TCPCHK error allocating check session");
3289 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3290 goto out_end_tcpcheck;
3291 }
Gaetan Rivet13a50432020-02-21 18:13:44 +01003292 vars_init(&check->vars, SCOPE_CHECK);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003293 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003294
3295 /* Preset tcp-check variables */
3296 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
3297 struct sample smp;
3298
3299 memset(&smp, 0, sizeof(smp));
3300 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
3301 smp.data = var->data;
3302 vars_set_by_name_ifexist(var->name.ptr, var->name.len, &smp);
3303 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003304 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003305
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003306 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003307 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003308
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003309 switch (rule->action) {
3310 case TCPCHK_ACT_CONNECT:
3311 check->current_step = rule;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003312
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003313 /* close but not release yet previous connection */
3314 if (check->cs) {
3315 cs_close(check->cs);
3316 retcode = -1; /* do not reuse the fd in the caller! */
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003317 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003318 eval_ret = tcpcheck_eval_connect(check, rule);
3319 must_read = 1; last_read = 0;
3320 break;
3321 case TCPCHK_ACT_SEND:
3322 check->current_step = rule;
3323 eval_ret = tcpcheck_eval_send(check, rule);
3324 must_read = 1;
3325 break;
3326 case TCPCHK_ACT_EXPECT:
3327 check->current_step = rule;
3328 if (must_read) {
3329 if (check->proxy->timeout.check)
3330 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003331
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003332 /* If we already subscribed, then we tried to received and
3333 * failed, so there's no point trying again.
3334 */
3335 if (check->wait_list.events & SUB_RETRY_RECV)
3336 goto out;
3337 if (conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0) <= 0) {
3338 if (conn->flags & (CO_FL_ERROR|CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
3339 last_read = 1;
3340 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
3341 /* Report network errors only if we got no other data. Otherwise
3342 * we'll let the upper layers decide whether the response is OK
3343 * or not. It is very common that an RST sent by the server is
3344 * reported as an error just after the last data chunk.
3345 */
3346 goto out_end_tcpcheck;
3347 }
3348 }
3349 else {
3350 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
3351 goto out;
3352 }
3353 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003354
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003355 /* buffer full, don't wait for more data */
3356 if (b_full(&check->bi))
3357 last_read = 1;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003358
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003359 /* Check that response body is not empty... */
3360 if (!b_data(&check->bi)) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003361 if (!last_read)
3362 goto out;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003363
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003364 /* empty response */
3365 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
3366 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003367 if (rule->comment)
3368 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003369 set_server_check_status(check, rule->expect.err_status, trash.area);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003370 ret = -1;
3371 goto out_end_tcpcheck;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003372 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003373 must_read = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003374 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003375
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003376 eval_ret = tcpcheck_eval_expect(check, rule, last_read);
3377 if (eval_ret == TCPCHK_EVAL_WAIT) {
3378 check->current_step = rule->expect.head;
3379 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003380 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003381 break;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003382 case TCPCHK_ACT_ACTION_KW:
3383 /* Don't update the current step */
3384 eval_ret = tcpcheck_eval_action_kw(check, rule);
3385 break;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003386 default:
3387 /* Otherwise, just go to the next one and don't update
3388 * the current step
3389 */
3390 eval_ret = TCPCHK_EVAL_CONTINUE;
3391 break;
3392 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003393
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003394 switch (eval_ret) {
3395 case TCPCHK_EVAL_CONTINUE:
3396 break;
3397 case TCPCHK_EVAL_WAIT:
3398 goto out;
3399 case TCPCHK_EVAL_STOP:
3400 goto out_end_tcpcheck;
Baptiste Assmann248f1172018-03-01 21:49:01 +01003401 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003402 }
3403
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003404 /* All rules was evaluated */
Christopher Fauletdf38f882020-04-07 16:04:38 +02003405 if (check->current_step) {
3406 rule = check->current_step;
3407
3408 if (rule->action == TCPCHK_ACT_EXPECT) {
3409 struct buffer *msg = alloc_trash_chunk();
3410
3411 if (msg)
3412 tcpcheck_onsuccess_message(msg, check, rule, ist(NULL));
3413 set_server_check_status(check, rule->expect.ok_status,
3414 (msg ? b_head(msg) : "(tcp-check)"));
3415 free_trash_chunk(msg);
3416 }
3417 else if (rule->action == TCPCHK_ACT_CONNECT) {
3418 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
3419 enum healthcheck_status status = ((conn && ssl_sock_is_ssl(conn)) ? HCHK_STATUS_L6OK : HCHK_STATUS_L4OK);
3420
3421 set_server_check_status(check, status, msg);
3422 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003423 }
Christopher Fauletec07e382020-04-07 14:56:26 +02003424 else
3425 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003426
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003427 out_end_tcpcheck:
Willy Tarreauef91c932019-07-23 14:37:47 +02003428 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003429 chk_report_conn_err(check, errno, 0);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003430
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003431 /* cleanup before leaving */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003432 check->current_step = NULL;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003433 if (check->sess != NULL) {
Gaetan Rivet13a50432020-02-21 18:13:44 +01003434 vars_prune(&check->vars, check->sess, NULL);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003435 session_free(check->sess);
3436 check->sess = NULL;
3437 }
3438 out:
3439 return retcode;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003440}
3441
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003442static const char *init_check(struct check *check, int type)
Simon Hormanb1900d52015-01-30 11:22:54 +09003443{
3444 check->type = type;
3445
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003446 b_reset(&check->bi); check->bi.size = global.tune.chksize;
3447 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Simon Hormanb1900d52015-01-30 11:22:54 +09003448
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003449 check->bi.area = calloc(check->bi.size, sizeof(char));
3450 check->bo.area = calloc(check->bo.size, sizeof(char));
3451
3452 if (!check->bi.area || !check->bo.area)
Simon Hormanb1900d52015-01-30 11:22:54 +09003453 return "out of memory while allocating check buffer";
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003454
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003455 check->wait_list.tasklet = tasklet_new();
3456 if (!check->wait_list.tasklet)
Ilya Shipitsind4259502020-04-08 01:07:56 +05003457 return "out of memory while allocating check tasklet";
Willy Tarreau4f6516d2018-12-19 13:59:17 +01003458 check->wait_list.events = 0;
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003459 check->wait_list.tasklet->process = event_srv_chk_io;
3460 check->wait_list.tasklet->context = check;
Simon Hormanb1900d52015-01-30 11:22:54 +09003461 return NULL;
3462}
3463
Simon Hormanbfb5d332015-01-30 11:22:55 +09003464void free_check(struct check *check)
3465{
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003466 task_destroy(check->task);
3467 if (check->wait_list.tasklet)
3468 tasklet_free(check->wait_list.tasklet);
3469
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003470 free(check->bi.area);
3471 free(check->bo.area);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003472 if (check->cs) {
3473 free(check->cs->conn);
3474 check->cs->conn = NULL;
3475 cs_free(check->cs);
3476 check->cs = NULL;
3477 }
Simon Hormanbfb5d332015-01-30 11:22:55 +09003478}
3479
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003480static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
3481{
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003482 struct logformat_node *lf, *lfb;
3483
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003484 if (!rule)
3485 return;
3486
3487 free(rule->comment);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003488 switch (rule->action) {
3489 case TCPCHK_ACT_SEND:
3490 switch (rule->send.type) {
3491 case TCPCHK_SEND_STRING:
3492 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003493 free(rule->send.data.ptr);
3494 break;
3495 case TCPCHK_SEND_STRING_LF:
3496 case TCPCHK_SEND_BINARY_LF:
3497 list_for_each_entry_safe(lf, lfb, &rule->send.fmt, list) {
3498 LIST_DEL(&lf->list);
3499 release_sample_expr(lf->expr);
3500 free(lf->arg);
3501 free(lf);
3502 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003503 break;
3504 case TCPCHK_SEND_UNDEF:
3505 break;
3506 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003507 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003508 case TCPCHK_ACT_EXPECT:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003509 list_for_each_entry_safe(lf, lfb, &rule->expect.onerror_fmt, list) {
3510 LIST_DEL(&lf->list);
3511 release_sample_expr(lf->expr);
3512 free(lf->arg);
3513 free(lf);
3514 }
3515 list_for_each_entry_safe(lf, lfb, &rule->expect.onsuccess_fmt, list) {
3516 LIST_DEL(&lf->list);
3517 release_sample_expr(lf->expr);
3518 free(lf->arg);
3519 free(lf);
3520 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003521 release_sample_expr(rule->expect.status_expr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003522 switch (rule->expect.type) {
3523 case TCPCHK_EXPECT_STRING:
3524 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003525 free(rule->expect.data.ptr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003526 break;
3527 case TCPCHK_EXPECT_REGEX:
3528 case TCPCHK_EXPECT_REGEX_BINARY:
3529 regex_free(rule->expect.regex);
3530 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003531 case TCPCHK_EXPECT_CUSTOM:
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003532 case TCPCHK_EXPECT_UNDEF:
3533 break;
3534 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003535 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003536 case TCPCHK_ACT_CONNECT:
Christopher Faulet79b31d42020-03-30 13:00:05 +02003537 free(rule->connect.sni);
Christopher Faulet98572322020-03-30 13:16:44 +02003538 free(rule->connect.alpn);
Christopher Fauletb7d30092020-03-30 15:19:03 +02003539 release_sample_expr(rule->connect.port_expr);
Christopher Faulet79b31d42020-03-30 13:00:05 +02003540 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003541 case TCPCHK_ACT_COMMENT:
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003542 break;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01003543 case TCPCHK_ACT_ACTION_KW:
3544 free(rule->action_kw.rule);
3545 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003546 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003547
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003548 if (in_pool)
3549 pool_free(pool_head_tcpcheck_rule, rule);
3550 else
3551 free(rule);
3552}
3553
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003554
Christopher Fauletce355072020-04-02 11:44:39 +02003555static struct tcpcheck_var *tcpcheck_var_create(const char *name)
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003556{
3557 struct tcpcheck_var *var = NULL;
3558
3559 var = calloc(1, sizeof(*var));
3560 if (var == NULL)
3561 return NULL;
3562
3563 var->name = ist2(strdup(name), strlen(name));
3564 if (var->name.ptr == NULL) {
3565 free(var);
3566 return NULL;
3567 }
3568
3569 LIST_INIT(&var->list);
3570 return var;
3571}
3572
3573static void tcpcheck_var_release(struct tcpcheck_var *var)
3574{
3575 if (!var)
3576 return;
3577
3578 free(var->name.ptr);
3579 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
3580 free(var->data.u.str.area);
3581 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
3582 free(var->data.u.meth.str.area);
3583 free(var);
3584}
3585
3586int dup_tcpcheck_vars(struct list *dst, struct list *src)
3587{
3588 struct tcpcheck_var *var, *new = NULL;
3589
3590 list_for_each_entry(var, src, list) {
3591 new = tcpcheck_var_create(var->name.ptr);
3592 if (!new)
3593 goto error;
3594 new->data.type = var->data.type;
3595 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
3596 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3597 goto error;
3598 if (var->data.type == SMP_T_STR)
3599 new->data.u.str.area[new->data.u.str.data] = 0;
3600 }
3601 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
3602 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3603 goto error;
3604 new->data.u.str.area[new->data.u.str.data] = 0;
3605 new->data.u.meth.meth = var->data.u.meth.meth;
3606 }
3607 else
3608 new->data.u = var->data.u;
3609 LIST_ADDQ(dst, &new->list);
3610 }
3611 return 1;
3612
3613 error:
3614 free(new);
3615 return 0;
3616}
3617
3618static void free_tcpcheck_vars(struct list *vars)
3619{
3620 struct tcpcheck_var *var, *back;
3621
3622 list_for_each_entry_safe(var, back, vars, list) {
3623 LIST_DEL(&var->list);
3624 tcpcheck_var_release(var);
3625 }
3626}
3627
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003628void email_alert_free(struct email_alert *alert)
3629{
3630 struct tcpcheck_rule *rule, *back;
3631
3632 if (!alert)
3633 return;
3634
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003635 if (alert->rules.list) {
3636 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
3637 LIST_DEL(&rule->list);
3638 free_tcpcheck(rule, 1);
3639 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003640 free_tcpcheck_vars(&alert->rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003641 free(alert->rules.list);
3642 alert->rules.list = NULL;
Christopher Fauletde1a75b2017-10-23 15:38:19 +02003643 }
Willy Tarreaubafbe012017-11-24 17:34:44 +01003644 pool_free(pool_head_email_alert, alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003645}
3646
Olivier Houchard9f6af332018-05-25 14:04:04 +02003647static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003648{
Olivier Houchard9f6af332018-05-25 14:04:04 +02003649 struct check *check = context;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003650 struct email_alertq *q;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003651 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003652
3653 q = container_of(check, typeof(*q), check);
3654
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003655 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003656 while (1) {
3657 if (!(check->state & CHK_ST_ENABLED)) {
3658 if (LIST_ISEMPTY(&q->email_alerts)) {
3659 /* All alerts processed, queue the task */
3660 t->expire = TICK_ETERNITY;
3661 task_queue(t);
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003662 goto end;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003663 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003664
3665 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003666 LIST_DEL(&alert->list);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003667 t->expire = now_ms;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003668 check->tcpcheck_rules = &alert->rules;
Olivier Houchard0923fa42019-01-11 18:43:04 +01003669 check->status = HCHK_STATUS_INI;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003670 check->state |= CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003671 }
3672
Olivier Houchard9f6af332018-05-25 14:04:04 +02003673 process_chk(t, context, state);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003674 if (check->state & CHK_ST_INPROGRESS)
3675 break;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003676
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003677 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003678 email_alert_free(alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003679 check->tcpcheck_rules = NULL;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003680 check->server = NULL;
3681 check->state &= ~CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003682 }
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003683 end:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003684 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003685 return t;
3686}
3687
Christopher Faulet0108bb32017-10-20 21:34:32 +02003688/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
3689 *
3690 * The function returns 1 in success case, otherwise, it returns 0 and err is
3691 * filled.
3692 */
3693int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003694{
Christopher Faulet0108bb32017-10-20 21:34:32 +02003695 struct mailer *mailer;
3696 struct email_alertq *queues;
3697 const char *err_str;
3698 int i = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003699
Christopher Faulet0108bb32017-10-20 21:34:32 +02003700 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
3701 memprintf(err, "out of memory while allocating mailer alerts queues");
mildis5ab01cb2018-10-02 16:46:34 +02003702 goto fail_no_queue;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003703 }
3704
Christopher Faulet0108bb32017-10-20 21:34:32 +02003705 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
3706 struct email_alertq *q = &queues[i];
3707 struct check *check = &q->check;
3708 struct task *t;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003709
3710 LIST_INIT(&q->email_alerts);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003711 HA_SPIN_INIT(&q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003712 check->inter = mls->timeout.mail;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003713 check->rise = DEF_AGENT_RISETIME;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01003714 check->proxy = p;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003715 check->fall = DEF_AGENT_FALLTIME;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003716 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
3717 memprintf(err, "%s", err_str);
3718 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003719 }
3720
3721 check->xprt = mailer->xprt;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003722 check->addr = mailer->addr;
Christopher Fauletb797ae12018-03-27 15:35:35 +02003723 check->port = get_host_port(&mailer->addr);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003724
Emeric Brunc60def82017-09-27 14:59:38 +02003725 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003726 memprintf(err, "out of memory while allocating mailer alerts task");
3727 goto error;
3728 }
3729
3730 check->task = t;
3731 t->process = process_email_alert;
3732 t->context = check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003733
Christopher Faulet0108bb32017-10-20 21:34:32 +02003734 /* check this in one ms */
3735 t->expire = TICK_ETERNITY;
3736 check->start = now;
3737 task_queue(t);
3738 }
3739
3740 mls->users++;
3741 free(p->email_alert.mailers.name);
3742 p->email_alert.mailers.m = mls;
3743 p->email_alert.queues = queues;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003744 return 0;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003745
3746 error:
3747 for (i = 0; i < mls->count; i++) {
3748 struct email_alertq *q = &queues[i];
3749 struct check *check = &q->check;
3750
Christopher Faulet0108bb32017-10-20 21:34:32 +02003751 free_check(check);
3752 }
3753 free(queues);
mildis5ab01cb2018-10-02 16:46:34 +02003754 fail_no_queue:
Christopher Faulet0108bb32017-10-20 21:34:32 +02003755 return 1;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003756}
3757
3758
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003759static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003760{
Gaetan Rivet4038b942020-02-26 16:19:40 +01003761 struct tcpcheck_rule *tcpcheck, *prev_check;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003762 struct tcpcheck_expect *expect;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003763
Willy Tarreaubafbe012017-11-24 17:34:44 +01003764 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003765 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003766 memset(tcpcheck, 0, sizeof(*tcpcheck));
Gaetan Rivetb616add2020-02-07 15:37:17 +01003767 tcpcheck->action = TCPCHK_ACT_EXPECT;
3768
3769 expect = &tcpcheck->expect;
3770 expect->type = TCPCHK_EXPECT_STRING;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003771 LIST_INIT(&expect->onerror_fmt);
3772 LIST_INIT(&expect->onsuccess_fmt);
Christopher Fauletec07e382020-04-07 14:56:26 +02003773 expect->ok_status = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003774 expect->err_status = HCHK_STATUS_L7RSP;
3775 expect->tout_status = HCHK_STATUS_L7TOUT;
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003776 expect->data = ist2(strdup(str), strlen(str));
3777 if (!expect->data.ptr) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003778 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003779 return 0;
3780 }
3781
Gaetan Rivet4038b942020-02-26 16:19:40 +01003782 /* All tcp-check expect points back to the first inverse expect rule
3783 * in a chain of one or more expect rule, potentially itself.
3784 */
Gaetan Rivetb616add2020-02-07 15:37:17 +01003785 tcpcheck->expect.head = tcpcheck;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003786 list_for_each_entry_rev(prev_check, rules->list, list) {
Gaetan Rivet4038b942020-02-26 16:19:40 +01003787 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet12d57402020-04-10 09:58:42 +02003788 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
Gaetan Rivetb616add2020-02-07 15:37:17 +01003789 tcpcheck->expect.head = prev_check;
Gaetan Rivet4038b942020-02-26 16:19:40 +01003790 continue;
3791 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003792 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet4038b942020-02-26 16:19:40 +01003793 break;
3794 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003795 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003796 return 1;
3797}
3798
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003799static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003800{
3801 struct tcpcheck_rule *tcpcheck;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003802 struct tcpcheck_send *send;
Willy Tarreau64345aa2016-08-10 19:29:09 +02003803 const char *in;
3804 char *dst;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003805 int i;
3806
Willy Tarreaubafbe012017-11-24 17:34:44 +01003807 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003808 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003809 memset(tcpcheck, 0, sizeof(*tcpcheck));
3810 tcpcheck->action = TCPCHK_ACT_SEND;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003811
3812 send = &tcpcheck->send;
3813 send->type = TCPCHK_SEND_STRING;
3814
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003815 for (i = 0; strs[i]; i++)
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003816 send->data.len += strlen(strs[i]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003817
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003818 send->data.ptr = malloc(send->data.len + 1);
3819 if (!isttest(send->data)) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003820 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003821 return 0;
3822 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003823
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003824 dst = send->data.ptr;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003825 for (i = 0; strs[i]; i++)
Willy Tarreau64345aa2016-08-10 19:29:09 +02003826 for (in = strs[i]; (*dst = *in++); dst++);
3827 *dst = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003828
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003829 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003830 return 1;
3831}
3832
Christopher Faulet0108bb32017-10-20 21:34:32 +02003833static int enqueue_one_email_alert(struct proxy *p, struct server *s,
3834 struct email_alertq *q, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003835{
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003836 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003837 struct tcpcheck_rule *tcpcheck;
3838 struct check *check = &q->check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003839
Willy Tarreaubafbe012017-11-24 17:34:44 +01003840 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003841 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003842 LIST_INIT(&alert->list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003843 alert->rules.flags = 0;
3844 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
3845 if (!alert->rules.list)
3846 goto error;
3847 LIST_INIT(alert->rules.list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003848 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
Christopher Faulet0108bb32017-10-20 21:34:32 +02003849 alert->srv = s;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003850
Willy Tarreaubafbe012017-11-24 17:34:44 +01003851 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003852 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003853 memset(tcpcheck, 0, sizeof(*tcpcheck));
3854 tcpcheck->action = TCPCHK_ACT_CONNECT;
3855 tcpcheck->comment = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003856
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003857 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003858
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003859 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003860 goto error;
3861
3862 {
3863 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003864 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003865 goto error;
3866 }
3867
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003868 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003869 goto error;
3870
3871 {
3872 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003873 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003874 goto error;
3875 }
3876
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003877 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003878 goto error;
3879
3880 {
3881 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003882 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003883 goto error;
3884 }
3885
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003886 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003887 goto error;
3888
3889 {
3890 const char * const strs[2] = { "DATA\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003891 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003892 goto error;
3893 }
3894
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003895 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003896 goto error;
3897
3898 {
3899 struct tm tm;
3900 char datestr[48];
3901 const char * const strs[18] = {
Pieter Baauw5e0964e2016-02-13 16:27:35 +01003902 "From: ", p->email_alert.from, "\r\n",
3903 "To: ", p->email_alert.to, "\r\n",
3904 "Date: ", datestr, "\r\n",
3905 "Subject: [HAproxy Alert] ", msg, "\r\n",
3906 "\r\n",
3907 msg, "\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003908 "\r\n",
Pieter Baauwed35c372015-07-22 19:51:54 +02003909 ".\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003910 NULL
3911 };
3912
3913 get_localtime(date.tv_sec, &tm);
3914
3915 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
3916 goto error;
3917 }
3918
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003919 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003920 goto error;
3921 }
3922
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003923 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003924 goto error;
3925
3926 {
3927 const char * const strs[2] = { "QUIT\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003928 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003929 goto error;
3930 }
3931
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003932 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003933 goto error;
3934
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003935 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003936 task_wakeup(check->task, TASK_WOKEN_MSG);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003937 LIST_ADDQ(&q->email_alerts, &alert->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003938 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003939 return 1;
3940
3941error:
3942 email_alert_free(alert);
3943 return 0;
3944}
3945
Christopher Faulet0108bb32017-10-20 21:34:32 +02003946static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003947{
3948 int i;
3949 struct mailer *mailer;
3950
3951 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
3952 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003953 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003954 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003955 return;
3956 }
3957 }
3958
3959 return;
3960}
3961
3962/*
3963 * Send email alert if configured.
3964 */
Simon Horman64e34162015-02-06 11:11:57 +09003965void send_email_alert(struct server *s, int level, const char *format, ...)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003966{
3967 va_list argp;
3968 char buf[1024];
3969 int len;
3970 struct proxy *p = s->proxy;
3971
Christopher Faulet0108bb32017-10-20 21:34:32 +02003972 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003973 return;
3974
3975 va_start(argp, format);
3976 len = vsnprintf(buf, sizeof(buf), format, argp);
3977 va_end(argp);
3978
Thierry FOURNIER62c8a212017-02-09 12:19:27 +01003979 if (len < 0 || len >= sizeof(buf)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003980 ha_alert("Email alert [%s] could not format message\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003981 return;
3982 }
3983
Christopher Faulet0108bb32017-10-20 21:34:32 +02003984 enqueue_email_alert(p, s, buf);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003985}
3986
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003987/*
3988 * Return value:
3989 * the port to be used for the health check
3990 * 0 in case no port could be found for the check
3991 */
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003992static int srv_check_healthcheck_port(struct check *chk)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003993{
3994 int i = 0;
3995 struct server *srv = NULL;
3996
3997 srv = chk->server;
3998
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003999 /* by default, we use the health check port ocnfigured */
4000 if (chk->port > 0)
4001 return chk->port;
4002
4003 /* try to get the port from check_core.addr if check.port not set */
4004 i = get_host_port(&chk->addr);
4005 if (i > 0)
4006 return i;
4007
4008 /* try to get the port from server address */
4009 /* prevent MAPPORTS from working at this point, since checks could
4010 * not be performed in such case (MAPPORTS impose a relative ports
4011 * based on live traffic)
4012 */
4013 if (srv->flags & SRV_F_MAPPORTS)
4014 return 0;
Willy Tarreau04276f32017-01-06 17:41:29 +01004015
4016 i = srv->svc_port; /* by default */
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004017 if (i > 0)
4018 return i;
4019
4020 return 0;
4021}
4022
Willy Tarreau172f5ce2018-11-26 11:21:50 +01004023REGISTER_POST_CHECK(start_checks);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02004024
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004025static int check_proxy_tcpcheck(struct proxy *px)
4026{
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004027 struct tcpcheck_rule *chk, *back;
4028 char *comment = NULL;
4029 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004030 int ret = 0;
4031
Christopher Faulet404f9192020-04-09 23:13:54 +02004032 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004033 goto out;
4034
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004035 if (!px->tcpcheck_rules.list) {
Christopher Faulet404f9192020-04-09 23:13:54 +02004036 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4037 ret |= ERR_ALERT | ERR_FATAL;
4038 goto out;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004039 }
4040
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004041 /* If there is no connect rule preceeding all send / expect rules, an
4042 * implicit one is inserted before all others
4043 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004044 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004045 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4046 chk = calloc(1, sizeof(*chk));
4047 if (!chk) {
4048 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4049 "(out of memory).\n", px->id);
4050 ret |= ERR_ALERT | ERR_FATAL;
4051 goto out;
4052 }
4053 chk->action = TCPCHK_ACT_CONNECT;
Christopher Fauletbb591a12020-04-01 16:52:17 +02004054 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004055 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004056 }
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004057
4058 /* Now remove comment rules */
4059 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4060 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4061 free(comment);
4062 comment = NULL;
4063 }
4064
4065 prev_action = chk->action;
4066 switch (chk->action) {
4067 case TCPCHK_ACT_COMMENT:
4068 free(comment);
4069 comment = chk->comment;
4070 LIST_DEL(&chk->list);
4071 free(chk);
4072 break;
4073 case TCPCHK_ACT_CONNECT:
4074 if (!chk->comment && comment)
4075 chk->comment = strdup(comment);
4076 /* fall though */
4077 case TCPCHK_ACT_ACTION_KW:
4078 free(comment);
4079 comment = NULL;
4080 break;
4081 case TCPCHK_ACT_SEND:
4082 case TCPCHK_ACT_EXPECT:
4083 if (!chk->comment && comment)
4084 chk->comment = strdup(comment);
4085 break;
4086 }
4087 }
4088 free(comment);
4089 comment = NULL;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004090
4091 out:
4092 return ret;
4093}
4094
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004095static int init_srv_check(struct server *srv)
4096{
4097 const char *err;
4098 struct tcpcheck_rule *r;
4099 int ret = 0;
4100
4101 if (!srv->do_check)
4102 goto out;
4103
4104
4105 /* If neither a port nor an addr was specified and no check transport
4106 * layer is forced, then the transport layer used by the checks is the
4107 * same as for the production traffic. Otherwise we use raw_sock by
4108 * default, unless one is specified.
4109 */
4110 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4111 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4112 srv->check.use_ssl = srv->use_ssl;
4113 srv->check.xprt = srv->xprt;
4114 }
4115 else if (srv->check.use_ssl == 1)
4116 srv->check.xprt = xprt_get(XPRT_SSL);
4117
4118 srv->check.send_proxy |= (srv->pp_opts);
4119 }
4120
4121 /* validate <srv> server health-check settings */
4122
4123 /* We need at least a service port, a check port or the first tcp-check
4124 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4125 */
4126 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4127 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4128 goto init;
4129
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004130 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004131 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4132 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4133 ret |= ERR_ALERT | ERR_ABORT;
4134 goto out;
4135 }
4136
4137 /* search the first action (connect / send / expect) in the list */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004138 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
Christopher Faulet5c288742020-03-31 08:15:58 +02004139 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004140 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4141 "nor tcp_check rule 'connect' with port information.\n",
4142 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4143 ret |= ERR_ALERT | ERR_ABORT;
4144 goto out;
4145 }
4146
4147 /* scan the tcp-check ruleset to ensure a port has been configured */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004148 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
Christopher Faulet5c288742020-03-31 08:15:58 +02004149 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004150 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4151 "and a tcp_check rule 'connect' with no port information.\n",
4152 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4153 ret |= ERR_ALERT | ERR_ABORT;
4154 goto out;
4155 }
4156 }
4157
4158 init:
4159 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4160 if (err) {
4161 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4162 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4163 ret |= ERR_ALERT | ERR_ABORT;
4164 goto out;
4165 }
4166 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4167 global.maxsock++;
4168
4169 out:
4170 return ret;
4171}
4172
4173static int init_srv_agent_check(struct server *srv)
4174{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004175 struct tcpcheck_rule *chk;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004176 const char *err;
4177 int ret = 0;
4178
4179 if (!srv->do_agent)
4180 goto out;
4181
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004182 /* If there is no connect rule preceeding all send / expect rules, an
4183 * implicit one is inserted before all others.
4184 */
4185 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4186 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4187 chk = calloc(1, sizeof(*chk));
4188 if (!chk) {
4189 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4190 " to agent-check for server '%s' (out of memory).\n",
4191 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4192 ret |= ERR_ALERT | ERR_FATAL;
4193 goto out;
4194 }
4195 chk->action = TCPCHK_ACT_CONNECT;
4196 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4197 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
4198 }
4199
4200
4201 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004202 if (err) {
4203 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4204 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4205 ret |= ERR_ALERT | ERR_ABORT;
4206 goto out;
4207 }
4208
4209 if (!srv->agent.inter)
4210 srv->agent.inter = srv->check.inter;
4211
4212 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4213 global.maxsock++;
4214
4215 out:
4216 return ret;
4217}
4218
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004219void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004220{
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02004221 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004222 px->tcpcheck_rules.flags = 0;
4223 px->tcpcheck_rules.list = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004224}
4225
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004226static void deinit_srv_check(struct server *srv)
4227{
Christopher Fauletce8111e2020-04-06 15:04:11 +02004228 if (srv->check.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004229 free_check(&srv->check);
Christopher Fauletce8111e2020-04-06 15:04:11 +02004230 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
4231 srv->do_check = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004232}
4233
4234
4235static void deinit_srv_agent_check(struct server *srv)
4236{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004237 if (srv->agent.tcpcheck_rules) {
4238 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
4239 free(srv->agent.tcpcheck_rules);
4240 srv->agent.tcpcheck_rules = NULL;
4241 }
4242
4243 if (srv->agent.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004244 free_check(&srv->agent);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004245
4246 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
4247 srv->do_agent = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004248}
4249
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004250static void deinit_tcpchecks()
4251{
4252 struct tcpcheck_ruleset *rs, *rsb;
4253 struct tcpcheck_rule *r, *rb;
4254
4255 list_for_each_entry_safe(rs, rsb, &tcpchecks_list, list) {
4256 LIST_DEL(&rs->list);
4257 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4258 LIST_DEL(&r->list);
4259 free_tcpcheck(r, 0);
4260 }
4261 free(rs->name);
4262 free(rs);
4263 }
4264}
4265
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004266
4267REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004268REGISTER_POST_SERVER_CHECK(init_srv_check);
4269REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
4270
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004271REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004272REGISTER_SERVER_DEINIT(deinit_srv_check);
4273REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004274REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004275
Christopher Faulet404f9192020-04-09 23:13:54 +02004276static struct tcpcheck_ruleset *tcpcheck_ruleset_lookup(const char *name)
4277{
4278 struct tcpcheck_ruleset *rs;
4279
4280 list_for_each_entry(rs, &tcpchecks_list, list) {
4281 if (strcmp(rs->name, name) == 0)
4282 return rs;
4283 }
4284 return NULL;
4285}
4286
4287static struct tcpcheck_ruleset *tcpcheck_ruleset_create(const char *name)
4288{
4289 struct tcpcheck_ruleset *rs;
4290
4291 rs = calloc(1, sizeof(*rs));
4292 if (rs == NULL)
4293 return NULL;
4294
4295 rs->name = strdup(name);
4296 if (rs->name == NULL) {
4297 free(rs);
4298 return NULL;
4299 }
4300
4301 LIST_INIT(&rs->list);
4302 LIST_INIT(&rs->rules);
4303 LIST_ADDQ(&tcpchecks_list, &rs->list);
4304 return rs;
4305}
4306
4307static void tcpcheck_ruleset_release(struct tcpcheck_ruleset *rs)
4308{
4309 struct tcpcheck_rule *r, *rb;
4310 if (!rs)
4311 return;
4312
4313 LIST_DEL(&rs->list);
4314 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4315 LIST_DEL(&r->list);
4316 free_tcpcheck(r, 0);
4317 }
4318 free(rs->name);
4319 free(rs);
4320}
4321
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004322/* extracts check payload at a fixed position and length */
4323static int
4324smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
4325{
4326 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
4327 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
4328 struct server *srv = (smp->sess ? objt_server(smp->sess->origin) : NULL);
4329 struct buffer *buf;
4330
4331 if (!srv || !srv->do_check)
4332 return 0;
4333
4334 buf = &srv->check.bi;
4335 if (buf_offset > b_data(buf))
4336 goto no_match;
4337 if (buf_offset + buf_size > b_data(buf))
4338 buf_size = 0;
4339
4340 /* init chunk as read only */
4341 smp->data.type = SMP_T_STR;
4342 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
4343 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
4344
4345 return 1;
4346
4347 no_match:
4348 smp->flags = 0;
4349 return 0;
4350}
4351
4352static struct sample_fetch_kw_list smp_kws = {ILH, {
4353 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
4354 { /* END */ },
4355}};
4356
4357INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
4358
4359
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004360struct action_kw_list tcp_check_keywords = {
4361 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
4362};
4363
4364/* Return the struct action_kw associated to a keyword */
4365static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
4366{
4367 return action_lookup(&tcp_check_keywords.list, kw);
4368}
4369
4370static void action_kw_tcp_check_build_list(struct buffer *chk)
4371{
4372 action_build_list(&tcp_check_keywords.list, chk);
4373}
4374
4375/* Create a tcp-check rule resulting from parsing a custom keyword. */
4376static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004377 struct list *rules, struct action_kw *kw,
4378 const char *file, int line, char **errmsg)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004379{
4380 struct tcpcheck_rule *chk = NULL;
4381 struct act_rule *actrule = NULL;
4382
4383 actrule = calloc(1, sizeof(*actrule));
4384 if (!actrule) {
4385 memprintf(errmsg, "out of memory");
4386 goto error;
4387 }
4388 actrule->kw = kw;
4389 actrule->from = ACT_F_TCP_CHK;
4390
4391 cur_arg++;
4392 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
4393 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
4394 goto error;
4395 }
4396
4397 chk = calloc(1, sizeof(*chk));
4398 if (!chk) {
4399 memprintf(errmsg, "out of memory");
4400 goto error;
4401 }
4402 chk->action = TCPCHK_ACT_ACTION_KW;
4403 chk->action_kw.rule = actrule;
4404 return chk;
4405
4406 error:
4407 free(actrule);
4408 return NULL;
4409}
4410
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004411static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Fauletb7d30092020-03-30 15:19:03 +02004412 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004413{
4414 struct tcpcheck_rule *chk = NULL;
Christopher Faulet5c288742020-03-31 08:15:58 +02004415 struct sockaddr_storage *sk = NULL;
Christopher Faulet98572322020-03-30 13:16:44 +02004416 char *comment = NULL, *sni = NULL, *alpn = NULL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004417 struct sample_expr *port_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004418 unsigned short conn_opts = 0;
4419 long port = 0;
Christopher Faulet98572322020-03-30 13:16:44 +02004420 int alpn_len = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004421
4422 list_for_each_entry(chk, rules, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004423 if (chk->action != TCPCHK_ACT_COMMENT && chk->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004424 break;
4425 }
4426 if (&chk->list != rules && chk->action != TCPCHK_ACT_CONNECT) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004427 memprintf(errmsg, "first step MUST also be a 'connect', "
4428 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
4429 "when there is a 'connect' step in the tcp-check ruleset");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004430 goto error;
4431 }
4432
4433 cur_arg++;
4434 while (*(args[cur_arg])) {
Christopher Fauletbb591a12020-04-01 16:52:17 +02004435 if (strcmp(args[cur_arg], "default") == 0)
4436 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
Christopher Faulet5c288742020-03-31 08:15:58 +02004437 else if (strcmp(args[cur_arg], "addr") == 0) {
4438 int port1, port2;
4439 struct protocol *proto;
4440
4441 if (!*(args[cur_arg+1])) {
4442 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
4443 goto error;
4444 }
4445
4446 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
4447 if (!sk) {
4448 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
4449 goto error;
4450 }
4451
4452 proto = protocol_by_family(sk->ss_family);
4453 if (!proto || !proto->connect) {
4454 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
4455 args[cur_arg]);
4456 goto error;
4457 }
4458
4459 if (port1 != port2) {
4460 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
4461 args[cur_arg], args[cur_arg+1]);
4462 goto error;
4463 }
4464
4465 cur_arg++;
4466 }
Christopher Faulet4dce5922020-03-30 13:54:42 +02004467 else if (strcmp(args[cur_arg], "port") == 0) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004468 const char *p, *end;
4469
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004470 if (!*(args[cur_arg+1])) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004471 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004472 goto error;
4473 }
4474 cur_arg++;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004475
4476 port = 0;
4477 release_sample_expr(port_expr);
4478 p = args[cur_arg]; end = p + strlen(p);
4479 port = read_uint(&p, end);
4480 if (p != end) {
4481 int idx = 0;
4482
4483 px->conf.args.ctx = ARGC_SRV;
4484 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4485 file, line, errmsg, &px->conf.args, NULL);
4486
4487 if (!port_expr) {
4488 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
4489 goto error;
4490 }
4491 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4492 memprintf(errmsg, "error detected while parsing port expression : "
4493 " fetch method '%s' extracts information from '%s', "
4494 "none of which is available here.\n",
4495 args[cur_arg], sample_src_names(port_expr->fetch->use));
4496 goto error;
4497 }
4498 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
4499 }
4500 else if (port > 65535 || port < 1) {
4501 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
4502 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004503 goto error;
4504 }
4505 }
4506 else if (strcmp(args[cur_arg], "comment") == 0) {
4507 if (!*(args[cur_arg+1])) {
4508 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4509 goto error;
4510 }
4511 cur_arg++;
4512 free(comment);
4513 comment = strdup(args[cur_arg]);
4514 if (!comment) {
4515 memprintf(errmsg, "out of memory");
4516 goto error;
4517 }
4518 }
4519 else if (strcmp(args[cur_arg], "send-proxy") == 0)
4520 conn_opts |= TCPCHK_OPT_SEND_PROXY;
Christopher Faulet085426a2020-03-30 13:07:02 +02004521 else if (strcmp(args[cur_arg], "via-socks4") == 0)
4522 conn_opts |= TCPCHK_OPT_SOCKS4;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004523 else if (strcmp(args[cur_arg], "linger") == 0)
4524 conn_opts |= TCPCHK_OPT_LINGER;
4525#ifdef USE_OPENSSL
4526 else if (strcmp(args[cur_arg], "ssl") == 0) {
4527 px->options |= PR_O_TCPCHK_SSL;
4528 conn_opts |= TCPCHK_OPT_SSL;
4529 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02004530 else if (strcmp(args[cur_arg], "sni") == 0) {
4531 if (!*(args[cur_arg+1])) {
4532 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4533 goto error;
4534 }
4535 cur_arg++;
4536 free(sni);
4537 sni = strdup(args[cur_arg]);
4538 if (!sni) {
4539 memprintf(errmsg, "out of memory");
4540 goto error;
4541 }
4542 }
Christopher Faulet98572322020-03-30 13:16:44 +02004543 else if (strcmp(args[cur_arg], "alpn") == 0) {
4544#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
4545 free(alpn);
4546 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
4547 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
4548 goto error;
4549 }
4550 cur_arg++;
4551#else
4552 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
4553 goto error;
4554#endif
4555 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004556#endif /* USE_OPENSSL */
4557
4558 else {
Christopher Faulet5c288742020-03-31 08:15:58 +02004559 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004560#ifdef USE_OPENSSL
Christopher Faulet98572322020-03-30 13:16:44 +02004561 ", 'ssl', 'sni', 'alpn'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004562#endif /* USE_OPENSSL */
Christopher Faulet4dce5922020-03-30 13:54:42 +02004563 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004564 args[cur_arg]);
4565 goto error;
4566 }
4567 cur_arg++;
4568 }
4569
4570 chk = calloc(1, sizeof(*chk));
4571 if (!chk) {
4572 memprintf(errmsg, "out of memory");
4573 goto error;
4574 }
Gaetan Rivet06d963a2020-02-21 18:49:05 +01004575 chk->action = TCPCHK_ACT_CONNECT;
4576 chk->comment = comment;
4577 chk->connect.port = port;
4578 chk->connect.options = conn_opts;
Christopher Faulet79b31d42020-03-30 13:00:05 +02004579 chk->connect.sni = sni;
Christopher Faulet98572322020-03-30 13:16:44 +02004580 chk->connect.alpn = alpn;
4581 chk->connect.alpn_len= alpn_len;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004582 chk->connect.port_expr= port_expr;
Christopher Faulet5c288742020-03-31 08:15:58 +02004583 if (sk)
4584 chk->connect.addr = *sk;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004585 return chk;
4586
4587 error:
Christopher Faulet98572322020-03-30 13:16:44 +02004588 free(alpn);
Christopher Faulet79b31d42020-03-30 13:00:05 +02004589 free(sni);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004590 free(comment);
Christopher Fauletb7d30092020-03-30 15:19:03 +02004591 release_sample_expr(port_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004592 return NULL;
4593}
4594
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004595static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004596 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004597{
4598 struct tcpcheck_rule *chk = NULL;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004599 char *comment = NULL, *data = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004600 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004601
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004602 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004603 if (!*(args[cur_arg+1])) {
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004604 memprintf(errmsg, "'%s' expects a %s as argument",
4605 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004606 goto error;
4607 }
4608
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004609 data = args[cur_arg+1];
4610
4611 cur_arg += 2;
4612 while (*(args[cur_arg])) {
4613 if (strcmp(args[cur_arg], "comment") == 0) {
4614 if (!*(args[cur_arg+1])) {
4615 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4616 goto error;
4617 }
4618 cur_arg++;
4619 free(comment);
4620 comment = strdup(args[cur_arg]);
4621 if (!comment) {
4622 memprintf(errmsg, "out of memory");
4623 goto error;
4624 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004625 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004626 else if (strcmp(args[cur_arg], "log-format") == 0) {
4627 if (type == TCPCHK_SEND_BINARY)
4628 type = TCPCHK_SEND_BINARY_LF;
4629 else if (type == TCPCHK_SEND_STRING)
4630 type = TCPCHK_SEND_STRING_LF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004631 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004632 else {
4633 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
4634 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004635 goto error;
4636 }
4637 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004638 }
4639
4640 chk = calloc(1, sizeof(*chk));
4641 if (!chk) {
4642 memprintf(errmsg, "out of memory");
4643 goto error;
4644 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004645 chk->action = TCPCHK_ACT_SEND;
4646 chk->comment = comment;
4647 chk->send.type = type;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004648
4649 switch (chk->send.type) {
4650 case TCPCHK_SEND_STRING:
4651 chk->send.data = ist2(strdup(data), strlen(data));
4652 if (!isttest(chk->send.data)) {
4653 memprintf(errmsg, "out of memory");
4654 goto error;
4655 }
4656 break;
4657 case TCPCHK_SEND_BINARY:
4658 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
4659 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
4660 goto error;
4661 }
4662 break;
4663 case TCPCHK_SEND_STRING_LF:
4664 case TCPCHK_SEND_BINARY_LF:
4665 LIST_INIT(&chk->send.fmt);
4666 px->conf.args.ctx = ARGC_SRV;
4667 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4668 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
4669 goto error;
4670 }
4671 break;
4672 case TCPCHK_SEND_UNDEF:
4673 goto error;
4674 }
4675
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004676 return chk;
4677
4678 error:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004679 free(chk);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004680 free(comment);
4681 return NULL;
4682}
4683
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004684static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
4685 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004686{
4687 struct tcpcheck_rule *chk = NULL;
4688 char *comment = NULL;
4689
4690 if (!*(args[cur_arg+1])) {
4691 memprintf(errmsg, "expects a string as argument");
4692 goto error;
4693 }
4694 cur_arg++;
4695 comment = strdup(args[cur_arg]);
4696 if (!comment) {
4697 memprintf(errmsg, "out of memory");
4698 goto error;
4699 }
4700
4701 chk = calloc(1, sizeof(*chk));
4702 if (!chk) {
4703 memprintf(errmsg, "out of memory");
4704 goto error;
4705 }
4706 chk->action = TCPCHK_ACT_COMMENT;
4707 chk->comment = comment;
4708 return chk;
4709
4710 error:
4711 free(comment);
4712 return NULL;
4713}
4714
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004715static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px, struct list *rules,
4716 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004717{
4718 struct tcpcheck_rule *prev_check, *chk = NULL;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004719 struct sample_expr *status_expr = NULL;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004720 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004721 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
Christopher Fauletec07e382020-04-07 14:56:26 +02004722 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004723 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
4724 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004725 long min_recv = -1;
4726 int inverse = 0, with_capture = 0;
4727
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004728 str = on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004729 if (!*(args[cur_arg+1])) {
4730 memprintf(errmsg, "expects at least a matching pattern as arguments");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004731 goto error;
4732 }
4733
4734 cur_arg++;
4735 while (*(args[cur_arg])) {
4736 int in_pattern = 0;
4737
4738 rescan:
4739 if (strcmp(args[cur_arg], "min-recv") == 0) {
4740 if (in_pattern) {
4741 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4742 goto error;
4743 }
4744 if (!*(args[cur_arg+1])) {
4745 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
4746 goto error;
4747 }
4748 /* Use an signed integer here because of chksize */
4749 cur_arg++;
4750 min_recv = atol(args[cur_arg]);
4751 if (min_recv < -1 || min_recv > INT_MAX) {
4752 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
4753 goto error;
4754 }
4755 }
4756 else if (*(args[cur_arg]) == '!') {
4757 in_pattern = 1;
4758 while (*(args[cur_arg]) == '!') {
4759 inverse = !inverse;
4760 args[cur_arg]++;
4761 }
4762 if (!*(args[cur_arg]))
4763 cur_arg++;
4764 goto rescan;
4765 }
4766 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "binary") == 0 ||
4767 strcmp(args[cur_arg], "rstring") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
4768 if (type != TCPCHK_EXPECT_UNDEF) {
4769 memprintf(errmsg, "only on pattern expected");
4770 goto error;
4771 }
4772 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING :
4773 ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY :
4774 ((*(args[cur_arg]+1) == 's') ? TCPCHK_EXPECT_REGEX : TCPCHK_EXPECT_REGEX_BINARY)));
4775
4776 if (!*(args[cur_arg+1])) {
4777 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4778 goto error;
4779 }
4780 cur_arg++;
4781 pattern = args[cur_arg];
4782 }
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004783 else if (strcmp(args[cur_arg], "custom") == 0) {
4784 if (in_pattern) {
4785 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4786 goto error;
4787 }
4788 if (type != TCPCHK_EXPECT_UNDEF) {
4789 memprintf(errmsg, "only on pattern expected");
4790 goto error;
4791 }
4792 type = TCPCHK_EXPECT_CUSTOM;
4793 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004794 else if (strcmp(args[cur_arg], "comment") == 0) {
4795 if (in_pattern) {
4796 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4797 goto error;
4798 }
4799 if (!*(args[cur_arg+1])) {
4800 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4801 goto error;
4802 }
4803 cur_arg++;
4804 free(comment);
4805 comment = strdup(args[cur_arg]);
4806 if (!comment) {
4807 memprintf(errmsg, "out of memory");
4808 goto error;
4809 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004810 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004811 else if (strcmp(args[cur_arg], "on-success") == 0) {
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 a string as argument", args[cur_arg]);
4818 goto error;
4819 }
4820 cur_arg++;
4821 free(on_success_msg);
4822 on_success_msg = strdup(args[cur_arg]);
4823 if (!on_success_msg) {
4824 memprintf(errmsg, "out of memory");
4825 goto error;
4826 }
4827 }
4828 else if (strcmp(args[cur_arg], "on-error") == 0) {
4829 if (in_pattern) {
4830 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4831 goto error;
4832 }
4833 if (!*(args[cur_arg+1])) {
4834 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4835 goto error;
4836 }
4837 cur_arg++;
4838 free(on_error_msg);
4839 on_error_msg = strdup(args[cur_arg]);
4840 if (!on_error_msg) {
4841 memprintf(errmsg, "out of memory");
4842 goto error;
4843 }
Christopher Fauletec07e382020-04-07 14:56:26 +02004844 }
4845 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4846 if (in_pattern) {
4847 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4848 goto error;
4849 }
4850 if (!*(args[cur_arg+1])) {
4851 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4852 goto error;
4853 }
4854 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4855 ok_st = HCHK_STATUS_L7OKD;
4856 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4857 ok_st = HCHK_STATUS_L7OKCD;
4858 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4859 ok_st = HCHK_STATUS_L6OK;
4860 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4861 ok_st = HCHK_STATUS_L4OK;
4862 else {
4863 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4864 args[cur_arg], args[cur_arg+1]);
4865 goto error;
4866 }
4867 cur_arg++;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004868 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004869 else if (strcmp(args[cur_arg], "error-status") == 0) {
4870 if (in_pattern) {
4871 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4872 goto error;
4873 }
4874 if (!*(args[cur_arg+1])) {
4875 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4876 goto error;
4877 }
4878 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4879 err_st = HCHK_STATUS_L7RSP;
4880 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4881 err_st = HCHK_STATUS_L7STS;
4882 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4883 err_st = HCHK_STATUS_L6RSP;
4884 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4885 err_st = HCHK_STATUS_L4CON;
4886 else {
4887 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4888 args[cur_arg], args[cur_arg+1]);
4889 goto error;
4890 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004891 cur_arg++;
4892 }
4893 else if (strcmp(args[cur_arg], "status-code") == 0) {
4894 int idx = 0;
4895
4896 if (in_pattern) {
4897 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4898 goto error;
4899 }
4900 if (!*(args[cur_arg+1])) {
4901 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4902 goto error;
4903 }
4904
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004905 cur_arg++;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004906 release_sample_expr(status_expr);
4907 px->conf.args.ctx = ARGC_SRV;
4908 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4909 file, line, errmsg, &px->conf.args, NULL);
4910 if (!status_expr) {
4911 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4912 goto error;
4913 }
4914 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4915 memprintf(errmsg, "error detected while parsing status-code expression : "
4916 " fetch method '%s' extracts information from '%s', "
4917 "none of which is available here.\n",
4918 args[cur_arg], sample_src_names(status_expr->fetch->use));
4919 goto error;
4920 }
4921 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004922 }
4923 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4924 if (in_pattern) {
4925 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4926 goto error;
4927 }
4928 if (!*(args[cur_arg+1])) {
4929 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4930 goto error;
4931 }
4932 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4933 tout_st = HCHK_STATUS_L7TOUT;
4934 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4935 tout_st = HCHK_STATUS_L6TOUT;
4936 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4937 tout_st = HCHK_STATUS_L4TOUT;
4938 else {
4939 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4940 args[cur_arg], args[cur_arg+1]);
4941 goto error;
4942 }
4943 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004944 }
4945 else {
4946 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4947 " or comment but got '%s' as argument.", args[cur_arg]);
4948 goto error;
4949 }
4950
4951 cur_arg++;
4952 }
4953
4954 if (comment) {
4955 char *p = comment;
4956
4957 while (*p) {
4958 if (*p == '\\') {
4959 p++;
4960 if (!*p || !isdigit((unsigned char)*p) ||
4961 (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
4962 memprintf(errmsg, "invalid backreference in 'comment' argument");
4963 goto error;
4964 }
4965 with_capture = 1;
4966 }
4967 p++;
4968 }
4969 if (with_capture && !inverse)
4970 memprintf(errmsg, "using backreference in a positive expect comment is useless");
4971 }
4972
4973 chk = calloc(1, sizeof(*chk));
4974 if (!chk) {
4975 memprintf(errmsg, "out of memory");
4976 goto error;
4977 }
4978 chk->action = TCPCHK_ACT_EXPECT;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004979 LIST_INIT(&chk->expect.onerror_fmt);
4980 LIST_INIT(&chk->expect.onsuccess_fmt);
4981 chk->comment = comment; comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004982 chk->expect.type = type;
4983 chk->expect.min_recv = min_recv;
Christopher Faulet12d57402020-04-10 09:58:42 +02004984 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
4985 chk->expect.flags |= (with_capture ? TCPCHK_EXPT_FL_CAP : 0);
Christopher Fauletec07e382020-04-07 14:56:26 +02004986 chk->expect.ok_status = ok_st;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004987 chk->expect.err_status = err_st;
4988 chk->expect.tout_status = tout_st;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004989 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004990
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004991 if (on_success_msg) {
4992 px->conf.args.ctx = ARGC_SRV;
4993 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4994 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4995 goto error;
4996 }
4997 free(on_success_msg);
4998 on_success_msg = NULL;
4999 }
5000 if (on_error_msg) {
5001 px->conf.args.ctx = ARGC_SRV;
5002 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
5003 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
5004 goto error;
5005 }
5006 free(on_error_msg);
5007 on_error_msg = NULL;
5008 }
5009
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005010 switch (chk->expect.type) {
5011 case TCPCHK_EXPECT_STRING:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02005012 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
5013 if (!chk->expect.data.ptr) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005014 memprintf(errmsg, "out of memory");
5015 goto error;
5016 }
5017 break;
5018 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02005019 if (parse_binary(pattern, &chk->expect.data.ptr, (int *)&chk->expect.data.len, errmsg) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005020 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
5021 goto error;
5022 }
5023 case TCPCHK_EXPECT_REGEX:
5024 case TCPCHK_EXPECT_REGEX_BINARY:
5025 chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
5026 if (!chk->expect.regex)
5027 goto error;
5028 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02005029 case TCPCHK_EXPECT_CUSTOM:
5030 chk->expect.custom = NULL; /* Must be defined by the caller ! */
5031 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005032 case TCPCHK_EXPECT_UNDEF:
5033 free(chk);
5034 memprintf(errmsg, "pattern not found");
5035 goto error;
5036 }
5037
5038 /* All tcp-check expect points back to the first inverse expect rule in
5039 * a chain of one or more expect rule, potentially itself.
5040 */
5041 chk->expect.head = chk;
5042 list_for_each_entry_rev(prev_check, rules, list) {
5043 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet12d57402020-04-10 09:58:42 +02005044 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005045 chk->expect.head = prev_check;
5046 continue;
5047 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01005048 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005049 break;
5050 }
5051 return chk;
5052
5053 error:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005054 free_tcpcheck(chk, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005055 free(str);
5056 free(comment);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005057 free(on_success_msg);
5058 free(on_error_msg);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005059 release_sample_expr(status_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005060 return NULL;
5061}
5062
5063/* Parses the "tcp-check" proxy keyword */
5064static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5065 struct proxy *defpx, const char *file, int line,
5066 char **errmsg)
5067{
Christopher Faulet404f9192020-04-09 23:13:54 +02005068 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005069 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005070 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005071
5072 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5073 ret = 1;
5074
Christopher Faulet404f9192020-04-09 23:13:54 +02005075 /* Deduce the ruleset name from the proxy info */
5076 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5077 ((curpx == defpx) ? "defaults" : curpx->id),
5078 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005079
Christopher Faulet404f9192020-04-09 23:13:54 +02005080 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5081 if (rs == NULL) {
5082 rs = tcpcheck_ruleset_create(b_orig(&trash));
5083 if (rs == NULL) {
5084 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005085 goto error;
5086 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005087 }
5088
Gaetan Rivet5301b012020-02-25 17:19:17 +01005089 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005090 if (!LIST_ISEMPTY(&rs->rules)) {
5091 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005092 index = chk->index + 1;
5093 }
5094
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005095 cur_arg = 1;
5096 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005097 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005098 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005099 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005100 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005101 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005102 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005103 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005104 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005105 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5106
5107 if (!kw) {
5108 action_kw_tcp_check_build_list(&trash);
5109 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5110 "%s%s. but got '%s'",
5111 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5112 goto error;
5113 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005114 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005115 }
5116
5117 if (!chk) {
5118 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5119 goto error;
5120 }
5121 ret = (*errmsg != NULL); /* Handle warning */
5122
5123 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005124 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005125 LIST_ADDQ(&rs->rules, &chk->list);
5126
5127 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5128 !(curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK)) {
5129 /* Use this ruleset if the proxy already has tcp-check enabled */
5130 curpx->tcpcheck_rules.list = &rs->rules;
5131 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5132 }
5133 else {
5134 /* mark this ruleset as unused for now */
5135 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5136 }
5137
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005138 return ret;
5139
5140 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005141 free_tcpcheck(chk, 0);
5142 tcpcheck_ruleset_release(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005143 return -1;
5144}
5145
Christopher Faulet51b129f2020-04-09 15:54:18 +02005146/* Parses the "http-check" proxy keyword */
5147static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5148 struct proxy *defpx, const char *file, int line,
5149 char **errmsg)
5150{
5151 int cur_arg, ret = 0;
5152
5153 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5154 ret = 1;
5155
5156 cur_arg = 1;
5157 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5158 /* enable a graceful server shutdown on an HTTP 404 response */
5159 curpx->options |= PR_O_DISABLE404;
5160 if (too_many_args(1, args, errmsg, NULL))
5161 goto error;
5162 }
5163 else if (strcmp(args[cur_arg], "send-state") == 0) {
5164 /* enable emission of the apparent state of a server in HTTP checks */
5165 curpx->options2 |= PR_O2_CHK_SNDST;
5166 if (too_many_args(1, args, errmsg, NULL))
5167 goto error;
5168 }
5169 else if (strcmp(args[cur_arg], "send") == 0) {
5170 free(curpx->check_hdrs);
5171 free(curpx->check_body);
5172 curpx->check_hdrs = curpx->check_body = NULL;
5173 curpx->check_hdrs_len = curpx->check_body_len = 0;
5174
5175 cur_arg++;
5176 while (*(args[cur_arg])) {
5177 if (strcmp(args[cur_arg], "hdr") == 0) {
5178 int hdr_len;
5179 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2])) {
5180 memprintf(errmsg, "'%s %s' : %s expects a name and a value as parameter.",
5181 args[0], args[1], args[cur_arg]);
5182 goto error;
5183 }
5184
5185 cur_arg++;
5186 hdr_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 4;
5187 curpx->check_hdrs = my_realloc2(curpx->check_hdrs, curpx->check_hdrs_len+hdr_len+1);
5188 if (curpx->check_hdrs == NULL) {
5189 memprintf(errmsg, "out of memory.");
5190 goto error;
5191 }
5192 snprintf(curpx->check_hdrs + curpx->check_hdrs_len, hdr_len+1, "%s: %s\r\n", args[cur_arg], args[cur_arg+1]);
5193 curpx->check_hdrs_len += hdr_len;
5194
5195 cur_arg++;
5196 }
5197 else if (strcmp(args[cur_arg], "body") == 0) {
5198 if (!*(args[cur_arg+1])) {
5199 memprintf(errmsg, "'%s %s' : %s expects a string as parameter.",
5200 args[0], args[1], args[cur_arg]);
5201 goto error;
5202 }
5203 cur_arg++;
5204 free(curpx->check_body);
5205 curpx->check_body = strdup(args[cur_arg]);
5206 curpx->check_body_len = strlen(args[cur_arg]);
5207 if (curpx->check_body == NULL) {
5208 memprintf(errmsg, "out of memory.");
5209 goto error;
5210 }
5211 }
5212 else {
5213 memprintf(errmsg, "'%s %s' only supports 'hdr' and 'body', found '%s'.",
5214 args[0], args[1], args[cur_arg]);
5215 goto error;
5216 }
5217 cur_arg++;
5218 }
5219 }
5220 else if (strcmp(args[cur_arg], "expect") == 0) {
5221 const char *ptr_arg;
5222
5223 if (curpx->options2 & PR_O2_EXP_TYPE) {
5224 memprintf(errmsg, "'%s %s' already specified.", args[0], args[1]);
5225 goto error;
5226 }
5227
5228 cur_arg++;
5229
5230 /* consider exclamation marks, sole or at the beginning of a word */
5231 while (*(ptr_arg = args[cur_arg])) {
5232 while (*ptr_arg == '!') {
5233 curpx->options2 ^= PR_O2_EXP_INV;
5234 ptr_arg++;
5235 }
5236 if (*ptr_arg)
5237 break;
5238 cur_arg++;
5239 }
5240
5241 /* now ptr_arg points to the beginning of a word past any possible
5242 * exclamation mark, and cur_arg is the argument which holds this word.
5243 */
5244 if (strcmp(ptr_arg, "status") == 0) {
5245 if (!*(args[cur_arg+1])) {
5246 memprintf(errmsg, "'%s %s %s' expects <string> as an argument.",
5247 args[0], args[1], ptr_arg);
5248 goto error;
5249 }
5250 curpx->options2 |= PR_O2_EXP_STS;
5251 free(curpx->expect_str);
5252 curpx->expect_str = strdup(args[cur_arg+1]);
5253 }
5254 else if (strcmp(ptr_arg, "string") == 0) {
5255 if (!*(args[cur_arg+1])) {
5256 memprintf(errmsg, "'%s %s %s' expects <string> as an argument.",
5257 args[0], args[1], ptr_arg);
5258 goto error;
5259 }
5260 curpx->options2 |= PR_O2_EXP_STR;
5261 free(curpx->expect_str);
5262 curpx->expect_str = strdup(args[cur_arg+1]);
5263 }
5264 else if (strcmp(ptr_arg, "rstatus") == 0) {
5265 if (!*(args[cur_arg+1])) {
5266 memprintf(errmsg, "'%s %s %s' expects <regex> as an argument.",
5267 args[0], args[1], ptr_arg);
5268 goto error;
5269 }
5270 curpx->options2 |= PR_O2_EXP_RSTS;
5271 free(curpx->expect_str);
5272 regex_free(curpx->expect_regex);
5273 curpx->expect_str = strdup(args[cur_arg+1]);
5274 if (!(curpx->expect_regex = regex_comp(args[cur_arg+1], 1, 1, errmsg))) {
5275 memprintf(errmsg, "'%s %s %s' : regular expression '%s': %s.",
5276 args[0], args[1], ptr_arg, args[cur_arg+1], *errmsg);
5277 goto error;
5278 }
5279 }
5280 else if (strcmp(ptr_arg, "rstring") == 0) {
5281 if (!*(args[cur_arg+1])) {
5282 memprintf(errmsg, "'%s %s %s' expects <regex> as an argument.",
5283 args[0], args[1], ptr_arg);
5284 goto error;
5285 }
5286 curpx->options2 |= PR_O2_EXP_RSTR;
5287 free(curpx->expect_str);
5288 regex_free(curpx->expect_regex);
5289 curpx->expect_str = strdup(args[cur_arg+1]);
5290 if (!(curpx->expect_regex = regex_comp(args[cur_arg+1], 1, 1, errmsg))) {
5291 memprintf(errmsg, "'%s %s %s' : regular expression '%s': %s.",
5292 args[0], args[1], ptr_arg, args[cur_arg + 1], *errmsg);
5293 goto error;
5294 }
5295 }
5296 else {
5297 memprintf(errmsg, "'%s %s' only supports [!] 'status', 'string', 'rstatus', 'rstring', found '%s'.",
5298 args[0], args[1], ptr_arg);
5299 goto error;
5300 }
5301 }
5302 else {
5303 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send', 'send-state' and 'expect'. but got '%s'.",
5304 args[0], args[1]);
5305 goto error;
5306 }
5307
5308 ret = (*errmsg != NULL); /* Handle warning */
5309 return ret;
5310
5311 error:
5312 return -1;
5313}
5314
Christopher Faulete9111b62020-04-09 18:12:08 +02005315/* Parses the "external-check" proxy keyword */
5316static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5317 struct proxy *defpx, const char *file, int line,
5318 char **errmsg)
5319{
5320 int cur_arg, ret = 0;
5321
5322 cur_arg = 1;
5323 if (!*(args[cur_arg])) {
5324 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5325 goto error;
5326 }
5327
5328 if (strcmp(args[cur_arg], "command") == 0) {
5329 if (too_many_args(2, args, errmsg, NULL))
5330 goto error;
5331 if (!*(args[cur_arg+1])) {
5332 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5333 goto error;
5334 }
5335 free(curpx->check_command);
5336 curpx->check_command = strdup(args[cur_arg+1]);
5337 }
5338 else if (strcmp(args[cur_arg], "path") == 0) {
5339 if (too_many_args(2, args, errmsg, NULL))
5340 goto error;
5341 if (!*(args[cur_arg+1])) {
5342 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5343 goto error;
5344 }
5345 free(curpx->check_path);
5346 curpx->check_path = strdup(args[cur_arg+1]);
5347 }
5348 else {
5349 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5350 args[0], args[1]);
5351 goto error;
5352 }
5353
5354 ret = (*errmsg != NULL); /* Handle warning */
5355 return ret;
5356
5357error:
5358 return -1;
5359}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005360
Christopher Faulet430e4802020-04-09 15:28:16 +02005361/* Parses the "option tcp-check" proxy keyword */
5362int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5363 const char *file, int line)
5364{
Christopher Faulet404f9192020-04-09 23:13:54 +02005365 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005366 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5367 int err_code = 0;
5368
5369 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5370 err_code |= ERR_WARN;
5371
5372 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5373 goto out;
5374
Christopher Faulet404f9192020-04-09 23:13:54 +02005375 curpx->options2 &= ~PR_O2_CHK_ANY;
5376 curpx->options2 |= PR_O2_TCPCHK_CHK;
5377
5378 if (!(rules->flags & TCPCHK_RULES_PROTO_CHK)) {
5379 /* If a tcp-check rulesset is already set, do nothing */
5380 if (rules->list)
5381 goto out;
5382
5383 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5384 * get it.
5385 */
5386 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5387 goto curpx_ruleset;
5388
5389 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5390 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5391 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5392 if (rs)
5393 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005394 }
5395
Christopher Faulet404f9192020-04-09 23:13:54 +02005396 curpx_ruleset:
5397 /* Deduce the ruleset name from the proxy info */
5398 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5399 ((curpx == defpx) ? "defaults" : curpx->id),
5400 curpx->conf.file, curpx->conf.line);
5401
5402 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5403 if (rs == NULL) {
5404 rs = tcpcheck_ruleset_create(b_orig(&trash));
5405 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005406 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5407 goto error;
5408 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005409 }
5410
Christopher Faulet404f9192020-04-09 23:13:54 +02005411 ruleset_found:
5412 free_tcpcheck_vars(&rules->preset_vars);
5413 rules->list = NULL;
5414 rules->flags = 0;
5415
Christopher Faulet430e4802020-04-09 15:28:16 +02005416 free(curpx->check_req);
5417 curpx->check_req = NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02005418
5419 rules->list = &rs->rules;
Christopher Faulet430e4802020-04-09 15:28:16 +02005420
5421 out:
5422 return err_code;
5423
5424 error:
5425 err_code |= ERR_ALERT | ERR_FATAL;
5426 goto out;
5427}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005428
5429/* Parses the "option redis-check" proxy keyword */
5430int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5431 const char *file, int line)
5432{
5433 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5434 static char *redis_res = "+PONG\r\n";
5435
5436 struct tcpcheck_ruleset *rs = NULL;
5437 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5438 struct tcpcheck_rule *chk;
5439 char *errmsg = NULL;
5440 int err_code = 0;
5441
5442 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5443 err_code |= ERR_WARN;
5444
5445 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5446 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005447
5448 curpx->options2 &= ~PR_O2_CHK_ANY;
5449 curpx->options2 |= PR_O2_TCPCHK_CHK;
5450
5451 free_tcpcheck_vars(&rules->preset_vars);
5452 rules->list = NULL;
5453 rules->flags = 0;
5454
5455 rs = tcpcheck_ruleset_lookup("*redis-check");
5456 if (rs)
5457 goto ruleset_found;
5458
5459 rs = tcpcheck_ruleset_create("*redis-check");
5460 if (rs == NULL) {
5461 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5462 goto error;
5463 }
5464
5465 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5466 1, curpx, &rs->rules, file, line, &errmsg);
5467 if (!chk) {
5468 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5469 goto error;
5470 }
5471 chk->index = 0;
5472 LIST_ADDQ(&rs->rules, &chk->list);
5473
5474 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5475 "error-status", "L7STS",
5476 "on-error", "%[check.payload(),cut_crlf]",
5477 "on-success", "Redis server is ok",
5478 ""},
5479 1, curpx, &rs->rules, file, line, &errmsg);
5480 if (!chk) {
5481 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5482 goto error;
5483 }
5484 chk->index = 1;
5485 LIST_ADDQ(&rs->rules, &chk->list);
5486
5487 LIST_ADDQ(&tcpchecks_list, &rs->list);
5488
5489 ruleset_found:
5490 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005491 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005492
5493 out:
5494 free(errmsg);
5495 return err_code;
5496
5497 error:
5498 tcpcheck_ruleset_release(rs);
5499 err_code |= ERR_ALERT | ERR_FATAL;
5500 goto out;
5501}
5502
Christopher Faulet811f78c2020-04-01 11:10:27 +02005503
5504/* Parses the "option ssl-hello-chk" proxy keyword */
5505int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5506 const char *file, int line)
5507{
5508 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5509 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5510 *
5511 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5512 */
5513 static char sslv3_client_hello[] = {
5514 "16" /* ContentType : 0x16 = Hanshake */
5515 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5516 "0079" /* ContentLength : 0x79 bytes after this one */
5517 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5518 "000075" /* HandshakeLength : 0x75 bytes after this one */
5519 "0300" /* Hello Version : 0x0300 = v3 */
5520 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5521 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5522 "00" /* Session ID length : empty (no session ID) */
5523 "004E" /* Cipher Suite Length : 78 bytes after this one */
5524 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5525 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5526 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5527 "000D" "000E" "000F" "0010" /* various bit lengths, */
5528 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5529 "0015" "0016" "0017" "0018"
5530 "0019" "001A" "001B" "002F"
5531 "0030" "0031" "0032" "0033"
5532 "0034" "0035" "0036" "0037"
5533 "0038" "0039" "003A"
5534 "01" /* Compression Length : 0x01 = 1 byte for types */
5535 "00" /* Compression Type : 0x00 = NULL compression */
5536 };
5537
5538 struct tcpcheck_ruleset *rs = NULL;
5539 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5540 struct tcpcheck_rule *chk;
5541 char *errmsg = NULL;
5542 int err_code = 0;
5543
5544 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5545 err_code |= ERR_WARN;
5546
5547 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5548 goto out;
5549
Christopher Faulet811f78c2020-04-01 11:10:27 +02005550 curpx->options2 &= ~PR_O2_CHK_ANY;
5551 curpx->options2 |= PR_O2_TCPCHK_CHK;
5552
5553 free_tcpcheck_vars(&rules->preset_vars);
5554 rules->list = NULL;
5555 rules->flags = 0;
5556
5557 rs = tcpcheck_ruleset_lookup("*ssl-hello-check");
5558 if (rs)
5559 goto ruleset_found;
5560
5561 rs = tcpcheck_ruleset_create("*ssl-hello-check");
5562 if (rs == NULL) {
5563 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5564 goto error;
5565 }
5566
5567 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5568 1, curpx, &rs->rules, file, line, &errmsg);
5569 if (!chk) {
5570 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5571 goto error;
5572 }
5573 chk->index = 0;
5574 LIST_ADDQ(&rs->rules, &chk->list);
5575
5576 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005577 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005578 "error-status", "L6RSP", "tout-status", "L6TOUT",
5579 ""},
5580 1, curpx, &rs->rules, file, line, &errmsg);
5581 if (!chk) {
5582 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5583 goto error;
5584 }
5585 chk->index = 1;
5586 LIST_ADDQ(&rs->rules, &chk->list);
5587
5588 LIST_ADDQ(&tcpchecks_list, &rs->list);
5589
5590 ruleset_found:
5591 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005592 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005593
5594 out:
5595 free(errmsg);
5596 return err_code;
5597
5598 error:
5599 tcpcheck_ruleset_release(rs);
5600 err_code |= ERR_ALERT | ERR_FATAL;
5601 goto out;
5602}
5603
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005604/* Parses the "option smtpchk" proxy keyword */
5605int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5606 const char *file, int line)
5607{
5608 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5609
5610 struct tcpcheck_ruleset *rs = NULL;
5611 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5612 struct tcpcheck_rule *chk;
5613 struct tcpcheck_var *var = NULL;
5614 char *cmd = NULL, *errmsg = NULL;
5615 int err_code = 0;
5616
5617 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5618 err_code |= ERR_WARN;
5619
5620 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5621 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005622
5623 curpx->options2 &= ~PR_O2_CHK_ANY;
5624 curpx->options2 |= PR_O2_TCPCHK_CHK;
5625
5626 free_tcpcheck_vars(&rules->preset_vars);
5627 rules->list = NULL;
5628 rules->flags = 0;
5629
5630 cur_arg += 2;
5631 if (*args[cur_arg] && *args[cur_arg+1] &&
5632 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5633 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5634 if (cmd)
5635 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
5636 }
5637 else {
5638 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
5639 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
5640 cmd = strdup("HELO localhost");
5641 }
5642
5643 var = tcpcheck_var_create("check.smtp_cmd");
5644 if (cmd == NULL || var == NULL) {
5645 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5646 goto error;
5647 }
5648 var->data.type = SMP_T_STR;
5649 var->data.u.str.area = cmd;
5650 var->data.u.str.data = strlen(cmd);
5651 LIST_INIT(&var->list);
5652 LIST_ADDQ(&rules->preset_vars, &var->list);
5653 cmd = NULL;
5654 var = NULL;
5655
5656 rs = tcpcheck_ruleset_lookup("*smtp-check");
5657 if (rs)
5658 goto ruleset_found;
5659
5660 rs = tcpcheck_ruleset_create("*smtp-check");
5661 if (rs == NULL) {
5662 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5663 goto error;
5664 }
5665
5666 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5667 1, curpx, &rs->rules, file, line, &errmsg);
5668 if (!chk) {
5669 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5670 goto error;
5671 }
5672 chk->index = 0;
5673 LIST_ADDQ(&rs->rules, &chk->list);
5674
5675 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
5676 "min-recv", "4",
5677 "error-status", "L7RSP",
5678 "on-error", "%[check.payload(),cut_crlf]",
5679 ""},
5680 1, curpx, &rs->rules, file, line, &errmsg);
5681 if (!chk) {
5682 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5683 goto error;
5684 }
5685 chk->index = 1;
5686 LIST_ADDQ(&rs->rules, &chk->list);
5687
5688 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
5689 "min-recv", "4",
5690 "error-status", "L7STS",
5691 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5692 "status-code", "check.payload(0,3)",
5693 ""},
5694 1, curpx, &rs->rules, file, line, &errmsg);
5695 if (!chk) {
5696 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5697 goto error;
5698 }
5699 chk->index = 2;
5700 LIST_ADDQ(&rs->rules, &chk->list);
5701
5702 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
5703 1, curpx, &rs->rules, file, line, &errmsg);
5704 if (!chk) {
5705 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5706 goto error;
5707 }
5708 chk->index = 3;
5709 LIST_ADDQ(&rs->rules, &chk->list);
5710
5711 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
5712 "min-recv", "4",
5713 "error-status", "L7STS",
5714 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5715 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5716 "status-code", "check.payload(0,3)",
5717 ""},
5718 1, curpx, &rs->rules, file, line, &errmsg);
5719 if (!chk) {
5720 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5721 goto error;
5722 }
5723 chk->index = 4;
5724 LIST_ADDQ(&rs->rules, &chk->list);
5725
5726 LIST_ADDQ(&tcpchecks_list, &rs->list);
5727
5728 ruleset_found:
5729 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005730 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005731
5732 out:
5733 free(errmsg);
5734 return err_code;
5735
5736 error:
5737 free(cmd);
5738 free(var);
5739 free_tcpcheck_vars(&rules->preset_vars);
5740 tcpcheck_ruleset_release(rs);
5741 err_code |= ERR_ALERT | ERR_FATAL;
5742 goto out;
5743}
Christopher Faulet811f78c2020-04-01 11:10:27 +02005744
Christopher Fauletce355072020-04-02 11:44:39 +02005745/* Parses the "option pgsql-check" proxy keyword */
5746int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5747 const char *file, int line)
5748{
5749 static char pgsql_req[] = {
5750 "%[var(check.plen),htonl,hex]" /* The packet length*/
5751 "00030000" /* the version 3.0 */
5752 "7573657200" /* "user" key */
5753 "%[var(check.username),hex]00" /* the username */
5754 "00"
5755 };
5756
5757 struct tcpcheck_ruleset *rs = NULL;
5758 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5759 struct tcpcheck_rule *chk;
5760 struct tcpcheck_var *var = NULL;
5761 char *user = NULL, *errmsg = NULL;
5762 size_t packetlen = 0;
5763 int err_code = 0;
5764
5765 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5766 err_code |= ERR_WARN;
5767
5768 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5769 goto out;
5770
Christopher Fauletce355072020-04-02 11:44:39 +02005771 curpx->options2 &= ~PR_O2_CHK_ANY;
5772 curpx->options2 |= PR_O2_TCPCHK_CHK;
5773
5774 free_tcpcheck_vars(&rules->preset_vars);
5775 rules->list = NULL;
5776 rules->flags = 0;
5777
5778 cur_arg += 2;
5779 if (!*args[cur_arg] || !*args[cur_arg+1]) {
5780 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
5781 file, line, args[0], args[1]);
5782 goto error;
5783 }
5784 if (strcmp(args[cur_arg], "user") == 0) {
5785 packetlen = 15 + strlen(args[cur_arg+1]);
5786 user = strdup(args[cur_arg+1]);
5787
5788 var = tcpcheck_var_create("check.username");
5789 if (user == NULL || var == NULL) {
5790 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5791 goto error;
5792 }
5793 var->data.type = SMP_T_STR;
5794 var->data.u.str.area = user;
5795 var->data.u.str.data = strlen(user);
5796 LIST_INIT(&var->list);
5797 LIST_ADDQ(&rules->preset_vars, &var->list);
5798 user = NULL;
5799 var = NULL;
5800
5801 var = tcpcheck_var_create("check.plen");
5802 if (var == NULL) {
5803 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5804 goto error;
5805 }
5806 var->data.type = SMP_T_SINT;
5807 var->data.u.sint = packetlen;
5808 LIST_INIT(&var->list);
5809 LIST_ADDQ(&rules->preset_vars, &var->list);
5810 var = NULL;
5811 }
5812 else {
5813 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
5814 file, line, args[0], args[1]);
5815 goto error;
5816 }
5817
5818 rs = tcpcheck_ruleset_lookup("*pgsql-check");
5819 if (rs)
5820 goto ruleset_found;
5821
5822 rs = tcpcheck_ruleset_create("*pgsql-check");
5823 if (rs == NULL) {
5824 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5825 goto error;
5826 }
5827
5828 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5829 1, curpx, &rs->rules, file, line, &errmsg);
5830 if (!chk) {
5831 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5832 goto error;
5833 }
5834 chk->index = 0;
5835 LIST_ADDQ(&rs->rules, &chk->list);
5836
5837 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
5838 1, curpx, &rs->rules, file, line, &errmsg);
5839 if (!chk) {
5840 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5841 goto error;
5842 }
5843 chk->index = 1;
5844 LIST_ADDQ(&rs->rules, &chk->list);
5845
5846 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
5847 "min-recv", "5",
5848 "error-status", "L7RSP",
5849 "on-error", "%[check.payload(6,0)]",
5850 ""},
5851 1, curpx, &rs->rules, file, line, &errmsg);
5852 if (!chk) {
5853 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5854 goto error;
5855 }
5856 chk->index = 2;
5857 LIST_ADDQ(&rs->rules, &chk->list);
5858
5859 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
5860 "min-recv", "9",
5861 "error-status", "L7STS",
5862 "on-success", "PostgreSQL server is ok",
5863 "on-error", "PostgreSQL unknown error",
5864 ""},
5865 1, curpx, &rs->rules, file, line, &errmsg);
5866 if (!chk) {
5867 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5868 goto error;
5869 }
5870 chk->index = 3;
5871 LIST_ADDQ(&rs->rules, &chk->list);
5872
5873 LIST_ADDQ(&tcpchecks_list, &rs->list);
5874
5875 ruleset_found:
5876 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005877 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02005878
5879 out:
5880 free(errmsg);
5881 return err_code;
5882
5883 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02005884 free(user);
5885 free(var);
5886 free_tcpcheck_vars(&rules->preset_vars);
5887 tcpcheck_ruleset_release(rs);
5888 err_code |= ERR_ALERT | ERR_FATAL;
5889 goto out;
5890}
5891
5892
5893/* Parses the "option mysql-check" proxy keyword */
5894int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5895 const char *file, int line)
5896{
5897 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
5898 * const char mysql40_client_auth_pkt[] = {
5899 * "\x0e\x00\x00" // packet length
5900 * "\x01" // packet number
5901 * "\x00\x00" // client capabilities
5902 * "\x00\x00\x01" // max packet
5903 * "haproxy\x00" // username (null terminated string)
5904 * "\x00" // filler (always 0x00)
5905 * "\x01\x00\x00" // packet length
5906 * "\x00" // packet number
5907 * "\x01" // COM_QUIT command
5908 * };
5909 */
5910 static char mysql40_rsname[] = "*mysql40-check";
5911 static char mysql40_req[] = {
5912 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5913 "0080" /* client capabilities */
5914 "000001" /* max packet */
5915 "%[var(check.username),hex]00" /* the username */
5916 "00" /* filler (always 0x00) */
5917 "010000" /* packet length*/
5918 "00" /* sequence ID */
5919 "01" /* COM_QUIT command */
5920 };
5921
5922 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
5923 * const char mysql41_client_auth_pkt[] = {
5924 * "\x0e\x00\x00\" // packet length
5925 * "\x01" // packet number
5926 * "\x00\x00\x00\x00" // client capabilities
5927 * "\x00\x00\x00\x01" // max packet
5928 * "\x21" // character set (UTF-8)
5929 * char[23] // All zeroes
5930 * "haproxy\x00" // username (null terminated string)
5931 * "\x00" // filler (always 0x00)
5932 * "\x01\x00\x00" // packet length
5933 * "\x00" // packet number
5934 * "\x01" // COM_QUIT command
5935 * };
5936 */
5937 static char mysql41_rsname[] = "*mysql41-check";
5938 static char mysql41_req[] = {
5939 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5940 "00820000" /* client capabilities */
5941 "00800001" /* max packet */
5942 "21" /* character set (UTF-8) */
5943 "000000000000000000000000" /* 23 bytes, al zeroes */
5944 "0000000000000000000000"
5945 "%[var(check.username),hex]00" /* the username */
5946 "00" /* filler (always 0x00) */
5947 "010000" /* packet length*/
5948 "00" /* sequence ID */
5949 "01" /* COM_QUIT command */
5950 };
5951
5952 struct tcpcheck_ruleset *rs = NULL;
5953 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5954 struct tcpcheck_rule *chk;
5955 struct tcpcheck_var *var = NULL;
5956 char *mysql_rsname = "*mysql-check";
5957 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
5958 int index = 0, err_code = 0;
5959
5960 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5961 err_code |= ERR_WARN;
5962
5963 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5964 goto out;
5965
Christopher Fauletf2b3be52020-04-02 18:07:37 +02005966 curpx->options2 &= ~PR_O2_CHK_ANY;
5967 curpx->options2 |= PR_O2_TCPCHK_CHK;
5968
5969 free_tcpcheck_vars(&rules->preset_vars);
5970 rules->list = NULL;
5971 rules->flags = 0;
5972
5973 cur_arg += 2;
5974 if (*args[cur_arg]) {
5975 char *user;
5976 int packetlen, userlen;
5977
5978 if (strcmp(args[cur_arg], "user") != 0) {
5979 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
5980 file, line, args[0], args[1], args[cur_arg]);
5981 goto error;
5982 }
5983
5984 if (*(args[cur_arg+1]) == 0) {
5985 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
5986 file, line, args[0], args[1], args[cur_arg]);
5987 goto error;
5988 }
5989
5990 hdr = calloc(4, sizeof(*hdr));
5991 user = strdup(args[cur_arg+1]);
5992 userlen = strlen(args[cur_arg+1]);
5993
5994 if (hdr == NULL || user == NULL) {
5995 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5996 goto error;
5997 }
5998
5999 if (*args[cur_arg+2]) {
6000 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6001 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6002 file, line, args[cur_arg], args[cur_arg+2]);
6003 goto error;
6004 }
6005 packetlen = userlen + 7 + 27;
6006 mysql_req = mysql41_req;
6007 mysql_rsname = mysql41_rsname;
6008 }
6009 else {
6010 packetlen = userlen + 7;
6011 mysql_req = mysql40_req;
6012 mysql_rsname = mysql40_rsname;
6013 }
6014
6015 hdr[0] = (unsigned char)(packetlen & 0xff);
6016 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6017 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6018 hdr[3] = 1;
6019
6020 var = tcpcheck_var_create("check.header");
6021 if (var == NULL) {
6022 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6023 goto error;
6024 }
6025 var->data.type = SMP_T_STR;
6026 var->data.u.str.area = hdr;
6027 var->data.u.str.data = 4;
6028 LIST_INIT(&var->list);
6029 LIST_ADDQ(&rules->preset_vars, &var->list);
6030 hdr = NULL;
6031 var = NULL;
6032
6033 var = tcpcheck_var_create("check.username");
6034 if (var == NULL) {
6035 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6036 goto error;
6037 }
6038 var->data.type = SMP_T_STR;
6039 var->data.u.str.area = user;
6040 var->data.u.str.data = strlen(user);
6041 LIST_INIT(&var->list);
6042 LIST_ADDQ(&rules->preset_vars, &var->list);
6043 user = NULL;
6044 var = NULL;
6045 }
6046
6047 rs = tcpcheck_ruleset_lookup(mysql_rsname);
6048 if (rs)
6049 goto ruleset_found;
6050
6051 rs = tcpcheck_ruleset_create(mysql_rsname);
6052 if (rs == NULL) {
6053 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6054 goto error;
6055 }
6056
6057 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6058 1, curpx, &rs->rules, file, line, &errmsg);
6059 if (!chk) {
6060 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6061 goto error;
6062 }
6063 chk->index = index++;
6064 LIST_ADDQ(&rs->rules, &chk->list);
6065
6066 if (mysql_req) {
6067 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6068 1, curpx, &rs->rules, file, line, &errmsg);
6069 if (!chk) {
6070 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6071 goto error;
6072 }
6073 chk->index = index++;
6074 LIST_ADDQ(&rs->rules, &chk->list);
6075 }
6076
6077 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6078 1, curpx, &rs->rules, file, line, &errmsg);
6079 if (!chk) {
6080 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6081 goto error;
6082 }
6083 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6084 chk->index = index++;
6085 LIST_ADDQ(&rs->rules, &chk->list);
6086
6087 if (mysql_req) {
6088 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6089 1, curpx, &rs->rules, file, line, &errmsg);
6090 if (!chk) {
6091 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6092 goto error;
6093 }
6094 chk->expect.custom = tcpcheck_mysql_expect_ok;
6095 chk->index = index++;
6096 LIST_ADDQ(&rs->rules, &chk->list);
6097 }
6098
6099 LIST_ADDQ(&tcpchecks_list, &rs->list);
6100
6101 ruleset_found:
6102 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006103 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006104
6105 out:
6106 free(errmsg);
6107 return err_code;
6108
6109 error:
6110 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006111 free(user);
6112 free(var);
6113 free_tcpcheck_vars(&rules->preset_vars);
6114 tcpcheck_ruleset_release(rs);
6115 err_code |= ERR_ALERT | ERR_FATAL;
6116 goto out;
6117}
6118
Christopher Faulet1997eca2020-04-03 23:13:50 +02006119int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6120 const char *file, int line)
6121{
6122 static char *ldap_req = "300C020101600702010304008000";
6123
6124 struct tcpcheck_ruleset *rs = NULL;
6125 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6126 struct tcpcheck_rule *chk;
6127 char *errmsg = NULL;
6128 int err_code = 0;
6129
6130 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6131 err_code |= ERR_WARN;
6132
6133 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6134 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006135
6136 curpx->options2 &= ~PR_O2_CHK_ANY;
6137 curpx->options2 |= PR_O2_TCPCHK_CHK;
6138
6139 free_tcpcheck_vars(&rules->preset_vars);
6140 rules->list = NULL;
6141 rules->flags = 0;
6142
6143 rs = tcpcheck_ruleset_lookup("*ldap-check");
6144 if (rs)
6145 goto ruleset_found;
6146
6147 rs = tcpcheck_ruleset_create("*ldap-check");
6148 if (rs == NULL) {
6149 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6150 goto error;
6151 }
6152
6153 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6154 1, curpx, &rs->rules, file, line, &errmsg);
6155 if (!chk) {
6156 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6157 goto error;
6158 }
6159 chk->index = 0;
6160 LIST_ADDQ(&rs->rules, &chk->list);
6161
6162 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6163 "min-recv", "14",
6164 "on-error", "Not LDAPv3 protocol",
6165 ""},
6166 1, curpx, &rs->rules, file, line, &errmsg);
6167 if (!chk) {
6168 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6169 goto error;
6170 }
6171 chk->index = 1;
6172 LIST_ADDQ(&rs->rules, &chk->list);
6173
6174 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6175 1, curpx, &rs->rules, file, line, &errmsg);
6176 if (!chk) {
6177 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6178 goto error;
6179 }
6180 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6181 chk->index = 2;
6182 LIST_ADDQ(&rs->rules, &chk->list);
6183
6184 LIST_ADDQ(&tcpchecks_list, &rs->list);
6185
6186 ruleset_found:
6187 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006188 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006189
6190 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006191 free(errmsg);
6192 return err_code;
6193
6194 error:
6195 tcpcheck_ruleset_release(rs);
6196 err_code |= ERR_ALERT | ERR_FATAL;
6197 goto out;
6198}
6199
6200int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6201 const char *file, int line)
6202{
6203 struct tcpcheck_ruleset *rs = NULL;
6204 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6205 struct tcpcheck_rule *chk;
6206 char *spop_req = NULL;
6207 char *errmsg = NULL;
6208 int spop_len = 0, err_code = 0;
6209
6210 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6211 err_code |= ERR_WARN;
6212
6213 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6214 goto out;
6215
Christopher Faulet267b01b2020-04-04 10:27:09 +02006216 curpx->options2 &= ~PR_O2_CHK_ANY;
6217 curpx->options2 |= PR_O2_TCPCHK_CHK;
6218
6219 free_tcpcheck_vars(&rules->preset_vars);
6220 rules->list = NULL;
6221 rules->flags = 0;
6222
6223
6224 rs = tcpcheck_ruleset_lookup("*spop-check");
6225 if (rs)
6226 goto ruleset_found;
6227
6228 rs = tcpcheck_ruleset_create("*spop-check");
6229 if (rs == NULL) {
6230 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6231 goto error;
6232 }
6233
6234 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6235 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6236 goto error;
6237 }
6238 chunk_reset(&trash);
6239 dump_binary(&trash, spop_req, spop_len);
6240 trash.area[trash.data] = '\0';
6241
6242 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6243 1, curpx, &rs->rules, file, line, &errmsg);
6244 if (!chk) {
6245 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6246 goto error;
6247 }
6248 chk->index = 0;
6249 LIST_ADDQ(&rs->rules, &chk->list);
6250
6251 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
6252 1, curpx, &rs->rules, file, line, &errmsg);
6253 if (!chk) {
6254 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6255 goto error;
6256 }
6257 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6258 chk->index = 1;
6259 LIST_ADDQ(&rs->rules, &chk->list);
6260
6261 LIST_ADDQ(&tcpchecks_list, &rs->list);
6262
6263 ruleset_found:
6264 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006265 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006266
6267 out:
6268 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006269 free(errmsg);
6270 return err_code;
6271
6272 error:
6273 tcpcheck_ruleset_release(rs);
6274 err_code |= ERR_ALERT | ERR_FATAL;
6275 goto out;
6276}
Christopher Fauletce355072020-04-02 11:44:39 +02006277
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006278int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6279 const char *file, int line)
6280{
6281 static const char *http_req = "OPTIONS / HTTP/1.0\r\n";
6282 int err_code = 0;
6283
6284 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6285 err_code |= ERR_WARN;
6286
6287 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6288 goto out;
6289
6290 /* use HTTP request to check servers' health */
6291 free(curpx->check_req);
6292 free(curpx->check_hdrs);
6293 free(curpx->check_body);
6294
6295 curpx->check_req = curpx->check_hdrs = curpx->check_body = NULL;
6296 curpx->check_len = curpx->check_hdrs_len = curpx->check_body_len = 0;
6297 curpx->options2 &= ~PR_O2_CHK_ANY;
6298 curpx->options2 |= PR_O2_HTTP_CHK;
6299
6300 cur_arg += 2;
6301 if (!*args[cur_arg]) { /* no argument */
6302 curpx->check_req = strdup(http_req); /* default request */
6303 curpx->check_len = strlen(http_req);
6304 }
6305 else if (!*args[cur_arg+1]) { /* one argument : URI */
6306 curpx->check_len = strlen(args[cur_arg]) + strlen("OPTIONS HTTP/1.0\r\n");
6307 curpx->check_req = malloc(curpx->check_len+1);
6308 curpx->check_len = snprintf(curpx->check_req, curpx->check_len+1,
6309 "OPTIONS %s HTTP/1.0\r\n", args[cur_arg]);
6310 }
6311 else if (!*args[cur_arg+2]) { /* two arguments : METHOD URI */
6312 curpx->check_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + strlen(" HTTP/1.0\r\n") + 1;
6313 curpx->check_req = malloc(curpx->check_len+1);
6314 curpx->check_len = snprintf(curpx->check_req, curpx->check_len+1,
6315 "%s %s HTTP/1.0\r\n", args[cur_arg], args[cur_arg+1]);
6316 }
6317 else { /* 3 arguments : METHOD URI HTTP_VER */
6318 char *hdrs = strstr(args[cur_arg+2], "\r\n");
6319 char *body = strstr(args[cur_arg+2], "\r\n\r\n");
6320
6321 if (hdrs || body) {
6322 ha_warning("parsing [%s:%d]: '%s %s' : hiding headers or body at the end of the version string is deprecated."
6323 " Please, consider to use 'http-check send' directive instead.\n",
6324 file, line, args[0], args[1]);
6325 err_code |= ERR_WARN;
6326 }
6327
6328 if (hdrs == body)
6329 hdrs = NULL;
6330 if (hdrs) {
6331 *hdrs = '\0';
6332 hdrs += 2;
6333 }
6334 if (body) {
6335 *body = '\0';
6336 body += 4;
6337 }
6338
6339 curpx->check_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + strlen(args[cur_arg+2]) + 4;
6340 curpx->check_req = malloc(curpx->check_len+1);
6341 snprintf(curpx->check_req, curpx->check_len+1, "%s %s %s\r\n",
6342 args[cur_arg], args[cur_arg+1], args[cur_arg+2]);
6343 if (hdrs) {
6344 curpx->check_hdrs_len = strlen(hdrs) + 2;
6345 curpx->check_hdrs = malloc(curpx->check_hdrs_len+1);
6346 snprintf(curpx->check_hdrs, curpx->check_hdrs_len+1, "%s\r\n", hdrs);
6347 }
6348 if (body) {
6349 curpx->check_body_len = strlen(body);
6350 curpx->check_body = strdup(body);
6351 }
6352 }
6353 out:
6354 return err_code;
6355
6356 error:
6357 err_code |= ERR_ALERT | ERR_FATAL;
6358 goto out;
6359}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006360
Christopher Faulet6f557912020-04-09 15:58:50 +02006361int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6362 const char *file, int line)
6363{
6364 int err_code = 0;
6365
6366 free(curpx->check_req);
6367 curpx->check_req = NULL;
6368 curpx->options2 &= ~PR_O2_CHK_ANY;
6369 curpx->options2 |= PR_O2_EXT_CHK;
6370 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6371 goto out;
6372
6373 out:
6374 return err_code;
6375}
6376
Christopher Fauletce8111e2020-04-06 15:04:11 +02006377/* Parse the "addr" server keyword */
6378static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6379 char **errmsg)
6380{
6381 struct sockaddr_storage *sk;
6382 struct protocol *proto;
6383 int port1, port2, err_code = 0;
6384
6385
6386 if (!*args[*cur_arg+1]) {
6387 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6388 goto error;
6389 }
6390
6391 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6392 if (!sk) {
6393 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6394 goto error;
6395 }
6396
6397 proto = protocol_by_family(sk->ss_family);
6398 if (!proto || !proto->connect) {
6399 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6400 args[*cur_arg], args[*cur_arg+1]);
6401 goto error;
6402 }
6403
6404 if (port1 != port2) {
6405 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6406 args[*cur_arg], args[*cur_arg+1]);
6407 goto error;
6408 }
6409
6410 srv->check.addr = srv->agent.addr = *sk;
6411 srv->flags |= SRV_F_CHECKADDR;
6412 srv->flags |= SRV_F_AGENTADDR;
6413
6414 out:
6415 return err_code;
6416
6417 error:
6418 err_code |= ERR_ALERT | ERR_FATAL;
6419 goto out;
6420}
6421
6422
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006423/* Parse the "agent-addr" server keyword */
6424static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6425 char **errmsg)
6426{
6427 int err_code = 0;
6428
6429 if (!*(args[*cur_arg+1])) {
6430 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6431 goto error;
6432 }
6433 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6434 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6435 goto error;
6436 }
6437
6438 out:
6439 return err_code;
6440
6441 error:
6442 err_code |= ERR_ALERT | ERR_FATAL;
6443 goto out;
6444}
6445
6446/* Parse the "agent-check" server keyword */
6447static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6448 char **errmsg)
6449{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006450 struct tcpcheck_ruleset *rs = NULL;
6451 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6452 struct tcpcheck_rule *chk;
6453 int err_code = 0;
6454
6455 if (srv->do_agent)
6456 goto out;
6457
6458 if (!rules) {
6459 rules = calloc(1, sizeof(*rules));
6460 if (!rules) {
6461 memprintf(errmsg, "out of memory.");
6462 goto error;
6463 }
6464 LIST_INIT(&rules->preset_vars);
6465 srv->agent.tcpcheck_rules = rules;
6466 }
6467 rules->list = NULL;
6468 rules->flags = 0;
6469
6470 rs = tcpcheck_ruleset_lookup("*agent-check");
6471 if (rs)
6472 goto ruleset_found;
6473
6474 rs = tcpcheck_ruleset_create("*agent-check");
6475 if (rs == NULL) {
6476 memprintf(errmsg, "out of memory.");
6477 goto error;
6478 }
6479
6480 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6481 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6482 if (!chk) {
6483 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6484 goto error;
6485 }
6486 chk->index = 0;
6487 LIST_ADDQ(&rs->rules, &chk->list);
6488
6489 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6490 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6491 if (!chk) {
6492 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6493 goto error;
6494 }
6495 chk->expect.custom = tcpcheck_agent_expect_reply;
6496 chk->index = 1;
6497 LIST_ADDQ(&rs->rules, &chk->list);
6498
6499 LIST_ADDQ(&tcpchecks_list, &rs->list);
6500
6501 ruleset_found:
6502 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006503 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006504 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006505
6506 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006507 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006508
6509 error:
6510 deinit_srv_agent_check(srv);
6511 tcpcheck_ruleset_release(rs);
6512 err_code |= ERR_ALERT | ERR_FATAL;
6513 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006514}
6515
6516/* Parse the "agent-inter" server keyword */
6517static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6518 char **errmsg)
6519{
6520 const char *err = NULL;
6521 unsigned int delay;
6522 int err_code = 0;
6523
6524 if (!*(args[*cur_arg+1])) {
6525 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6526 goto error;
6527 }
6528
6529 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6530 if (err == PARSE_TIME_OVER) {
6531 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6532 args[*cur_arg+1], args[*cur_arg], srv->id);
6533 goto error;
6534 }
6535 else if (err == PARSE_TIME_UNDER) {
6536 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6537 args[*cur_arg+1], args[*cur_arg], srv->id);
6538 goto error;
6539 }
6540 else if (err) {
6541 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6542 *err, srv->id);
6543 goto error;
6544 }
6545 if (delay <= 0) {
6546 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6547 delay, args[*cur_arg], srv->id);
6548 goto error;
6549 }
6550 srv->agent.inter = delay;
6551
6552 out:
6553 return err_code;
6554
6555 error:
6556 err_code |= ERR_ALERT | ERR_FATAL;
6557 goto out;
6558}
6559
6560/* Parse the "agent-port" server keyword */
6561static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6562 char **errmsg)
6563{
6564 int err_code = 0;
6565
6566 if (!*(args[*cur_arg+1])) {
6567 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6568 goto error;
6569 }
6570
6571 global.maxsock++;
6572 srv->agent.port = atol(args[*cur_arg+1]);
6573
6574 out:
6575 return err_code;
6576
6577 error:
6578 err_code |= ERR_ALERT | ERR_FATAL;
6579 goto out;
6580}
6581
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006582int set_srv_agent_send(struct server *srv, const char *send)
6583{
6584 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6585 struct tcpcheck_var *var = NULL;
6586 char *str;
6587
6588 str = strdup(send);
6589 var = tcpcheck_var_create("check.agent_string");
6590 if (str == NULL || var == NULL)
6591 goto error;
6592
6593 free_tcpcheck_vars(&rules->preset_vars);
6594
6595 var->data.type = SMP_T_STR;
6596 var->data.u.str.area = str;
6597 var->data.u.str.data = strlen(str);
6598 LIST_INIT(&var->list);
6599 LIST_ADDQ(&rules->preset_vars, &var->list);
6600
6601 return 1;
6602
6603 error:
6604 free(str);
6605 free(var);
6606 return 0;
6607}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006608
6609/* Parse the "agent-send" server keyword */
6610static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6611 char **errmsg)
6612{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006613 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006614 int err_code = 0;
6615
6616 if (!*(args[*cur_arg+1])) {
6617 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
6618 goto error;
6619 }
6620
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006621 if (!rules) {
6622 rules = calloc(1, sizeof(*rules));
6623 if (!rules) {
6624 memprintf(errmsg, "out of memory.");
6625 goto error;
6626 }
6627 LIST_INIT(&rules->preset_vars);
6628 srv->agent.tcpcheck_rules = rules;
6629 }
6630
6631 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006632 memprintf(errmsg, "out of memory.");
6633 goto error;
6634 }
6635
6636 out:
6637 return err_code;
6638
6639 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006640 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006641 err_code |= ERR_ALERT | ERR_FATAL;
6642 goto out;
6643}
6644
6645/* Parse the "no-agent-send" server keyword */
6646static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6647 char **errmsg)
6648{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006649 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006650 return 0;
6651}
6652
Christopher Fauletce8111e2020-04-06 15:04:11 +02006653/* Parse the "check" server keyword */
6654static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6655 char **errmsg)
6656{
6657 srv->do_check = 1;
6658 return 0;
6659}
6660
6661/* Parse the "check-send-proxy" server keyword */
6662static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6663 char **errmsg)
6664{
6665 srv->check.send_proxy = 1;
6666 return 0;
6667}
6668
6669/* Parse the "check-via-socks4" server keyword */
6670static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6671 char **errmsg)
6672{
6673 srv->check.via_socks4 = 1;
6674 return 0;
6675}
6676
6677/* Parse the "no-check" server keyword */
6678static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6679 char **errmsg)
6680{
6681 deinit_srv_check(srv);
6682 return 0;
6683}
6684
6685/* Parse the "no-check-send-proxy" server keyword */
6686static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6687 char **errmsg)
6688{
6689 srv->check.send_proxy = 0;
6690 return 0;
6691}
6692
6693/* Parse the "rise" server keyword */
6694static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6695 char **errmsg)
6696{
6697 int err_code = 0;
6698
6699 if (!*args[*cur_arg + 1]) {
6700 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6701 goto error;
6702 }
6703
6704 srv->check.rise = atol(args[*cur_arg+1]);
6705 if (srv->check.rise <= 0) {
6706 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6707 goto error;
6708 }
6709
6710 if (srv->check.health)
6711 srv->check.health = srv->check.rise;
6712
6713 out:
6714 return err_code;
6715
6716 error:
6717 deinit_srv_agent_check(srv);
6718 err_code |= ERR_ALERT | ERR_FATAL;
6719 goto out;
6720 return 0;
6721}
6722
6723/* Parse the "fall" server keyword */
6724static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6725 char **errmsg)
6726{
6727 int err_code = 0;
6728
6729 if (!*args[*cur_arg + 1]) {
6730 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6731 goto error;
6732 }
6733
6734 srv->check.fall = atol(args[*cur_arg+1]);
6735 if (srv->check.fall <= 0) {
6736 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6737 goto error;
6738 }
6739
6740 out:
6741 return err_code;
6742
6743 error:
6744 deinit_srv_agent_check(srv);
6745 err_code |= ERR_ALERT | ERR_FATAL;
6746 goto out;
6747 return 0;
6748}
6749
6750/* Parse the "inter" server keyword */
6751static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6752 char **errmsg)
6753{
6754 const char *err = NULL;
6755 unsigned int delay;
6756 int err_code = 0;
6757
6758 if (!*(args[*cur_arg+1])) {
6759 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6760 goto error;
6761 }
6762
6763 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6764 if (err == PARSE_TIME_OVER) {
6765 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6766 args[*cur_arg+1], args[*cur_arg], srv->id);
6767 goto error;
6768 }
6769 else if (err == PARSE_TIME_UNDER) {
6770 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6771 args[*cur_arg+1], args[*cur_arg], srv->id);
6772 goto error;
6773 }
6774 else if (err) {
6775 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6776 *err, srv->id);
6777 goto error;
6778 }
6779 if (delay <= 0) {
6780 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6781 delay, args[*cur_arg], srv->id);
6782 goto error;
6783 }
6784 srv->check.inter = delay;
6785
6786 out:
6787 return err_code;
6788
6789 error:
6790 err_code |= ERR_ALERT | ERR_FATAL;
6791 goto out;
6792}
6793
6794
6795/* Parse the "fastinter" server keyword */
6796static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6797 char **errmsg)
6798{
6799 const char *err = NULL;
6800 unsigned int delay;
6801 int err_code = 0;
6802
6803 if (!*(args[*cur_arg+1])) {
6804 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6805 goto error;
6806 }
6807
6808 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6809 if (err == PARSE_TIME_OVER) {
6810 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6811 args[*cur_arg+1], args[*cur_arg], srv->id);
6812 goto error;
6813 }
6814 else if (err == PARSE_TIME_UNDER) {
6815 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6816 args[*cur_arg+1], args[*cur_arg], srv->id);
6817 goto error;
6818 }
6819 else if (err) {
6820 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6821 *err, srv->id);
6822 goto error;
6823 }
6824 if (delay <= 0) {
6825 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6826 delay, args[*cur_arg], srv->id);
6827 goto error;
6828 }
6829 srv->check.fastinter = delay;
6830
6831 out:
6832 return err_code;
6833
6834 error:
6835 err_code |= ERR_ALERT | ERR_FATAL;
6836 goto out;
6837}
6838
6839
6840/* Parse the "downinter" server keyword */
6841static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6842 char **errmsg)
6843{
6844 const char *err = NULL;
6845 unsigned int delay;
6846 int err_code = 0;
6847
6848 if (!*(args[*cur_arg+1])) {
6849 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6850 goto error;
6851 }
6852
6853 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6854 if (err == PARSE_TIME_OVER) {
6855 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6856 args[*cur_arg+1], args[*cur_arg], srv->id);
6857 goto error;
6858 }
6859 else if (err == PARSE_TIME_UNDER) {
6860 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6861 args[*cur_arg+1], args[*cur_arg], srv->id);
6862 goto error;
6863 }
6864 else if (err) {
6865 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6866 *err, srv->id);
6867 goto error;
6868 }
6869 if (delay <= 0) {
6870 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6871 delay, args[*cur_arg], srv->id);
6872 goto error;
6873 }
6874 srv->check.downinter = delay;
6875
6876 out:
6877 return err_code;
6878
6879 error:
6880 err_code |= ERR_ALERT | ERR_FATAL;
6881 goto out;
6882}
6883
6884/* Parse the "port" server keyword */
6885static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6886 char **errmsg)
6887{
6888 int err_code = 0;
6889
6890 if (!*(args[*cur_arg+1])) {
6891 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6892 goto error;
6893 }
6894
6895 global.maxsock++;
6896 srv->check.port = atol(args[*cur_arg+1]);
6897 srv->flags |= SRV_F_CHECKPORT;
6898
6899 out:
6900 return err_code;
6901
6902 error:
6903 err_code |= ERR_ALERT | ERR_FATAL;
6904 goto out;
6905}
6906
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006907static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02006908 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
6909 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
6910 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006911 { 0, NULL, NULL },
6912}};
6913
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006914static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02006915 { "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 +02006916 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
6917 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
6918 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
6919 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
6920 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006921 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
6922 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
6923 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006924 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006925 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
6926 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
6927 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
6928 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
6929 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
6930 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
6931 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
6932 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006933 { NULL, NULL, 0 },
6934}};
6935
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006936INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006937INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006938
Willy Tarreaubd741542010-03-16 18:46:54 +01006939/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02006940 * Local variables:
6941 * c-indent-level: 8
6942 * c-basic-offset: 8
6943 * End:
6944 */