blob: 6f46ef37eeaea441085bb0f5c99d4b9d6d24b9c0 [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
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001128/* returns the NON-COMMENT tcp-check rule from list <list> following <start> or
1129 * NULL if non was found. If <start> is NULL, it relies on
1130 * get_first_tcpcheck_rule().
1131 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001132static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001133{
1134 struct tcpcheck_rule *r;
1135
1136 if (!start)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001137 return get_first_tcpcheck_rule(rules);
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001138
1139 r = LIST_NEXT(&start->list, typeof(r), list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001140 list_for_each_entry_from(r, rules->list, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01001141 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001142 return r;
1143 }
1144 return NULL;
1145}
1146
Willy Tarreau2e993902011-10-31 11:53:20 +01001147/*
Simon Horman98637e52014-06-20 12:30:16 +09001148 * establish a server health-check that makes use of a connection.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001149 *
1150 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001151 * - SF_ERR_NONE if everything's OK and tcpcheck_main() was not called
1152 * - SF_ERR_UP if if everything's OK and tcpcheck_main() was called
1153 * - SF_ERR_SRVTO if there are no more servers
1154 * - SF_ERR_SRVCL if the connection was refused by the server
1155 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1156 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1157 * - SF_ERR_INTERNAL for any other purely internal errors
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001158 * - SF_ERR_CHK_PORT if no port could be found to run a health check on an AF_INET* socket
Tim Düsterhus4896c442016-11-29 02:15:19 +01001159 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001160 * Note that we try to prevent the network stack from sending the ACK during the
1161 * connect() when a pure TCP check is used (without PROXY protocol).
1162 */
Simon Horman98637e52014-06-20 12:30:16 +09001163static int connect_conn_chk(struct task *t)
Simon Hormanb00d17a2014-06-13 16:18:16 +09001164{
1165 struct check *check = t->context;
1166 struct server *s = check->server;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001167 struct conn_stream *cs = check->cs;
1168 struct connection *conn = cs_conn(cs);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001169 struct protocol *proto;
1170 int ret;
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001171 int connflags = 0;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001172
Willy Tarreau00149122017-10-04 18:05:01 +02001173 /* we cannot have a connection here */
1174 if (conn)
1175 return SF_ERR_INTERNAL;
1176
Simon Hormanb00d17a2014-06-13 16:18:16 +09001177 /* prepare the check buffer.
1178 * This should not be used if check is the secondary agent check
1179 * of a server as s->proxy->check_req will relate to the
1180 * configuration of the primary check. Similarly, tcp-check uses
1181 * its own strings.
1182 */
1183 if (check->type && check->type != PR_O2_TCPCHK_CHK && !(check->state & CHK_ST_AGENT)) {
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001184 b_putblk(&check->bo, s->proxy->check_req, s->proxy->check_len);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001185
Christopher Faulet811f78c2020-04-01 11:10:27 +02001186 /* we want to check if this host replies to HTTP requests
Simon Hormanb00d17a2014-06-13 16:18:16 +09001187 * so we'll send the request, and won't wake the checker up now.
1188 */
Christopher Faulet811f78c2020-04-01 11:10:27 +02001189 if ((check->type) == PR_O2_HTTP_CHK) {
Cyril Bonté32602d22015-01-30 00:07:07 +01001190 /* prevent HTTP keep-alive when "http-check expect" is used */
1191 if (s->proxy->options2 & PR_O2_EXP_TYPE)
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001192 b_putist(&check->bo, ist("Connection: close\r\n"));
Christopher Faulet8acb1282020-04-09 08:44:06 +02001193
1194 /* If there is a body, add its content-length */
1195 if (s->proxy->check_body_len)
1196 chunk_appendf(&check->bo, "Content-Length: %s\r\n", ultoa(s->proxy->check_body_len));
1197
1198 /* Add configured headers */
1199 if (s->proxy->check_hdrs)
1200 b_putblk(&check->bo, s->proxy->check_hdrs, s->proxy->check_hdrs_len);
1201
1202 /* Add send-state header */
1203 if (s->proxy->options2 & PR_O2_CHK_SNDST)
1204 b_putblk(&check->bo, trash.area,
1205 httpchk_build_status_header(s, trash.area, trash.size));
1206
1207 /* end-of-header */
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001208 b_putist(&check->bo, ist("\r\n"));
Christopher Faulet8acb1282020-04-09 08:44:06 +02001209
1210 /* Add the body */
1211 if (s->proxy->check_body)
1212 b_putblk(&check->bo, s->proxy->check_body, s->proxy->check_body_len);
1213
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001214 *b_tail(&check->bo) = '\0'; /* to make gdb output easier to read */
Simon Hormanb00d17a2014-06-13 16:18:16 +09001215 }
1216 }
1217
Willy Tarreauf411cce2017-10-04 16:21:19 +02001218 /* for tcp-checks, the initial connection setup is handled separately as
1219 * it may be sent to a specific port and not to the server's.
1220 */
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001221 if (check->type == PR_O2_TCPCHK_CHK) {
1222 /* tcpcheck initialisation */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02001223 check->current_step = NULL;
Willy Tarreauf411cce2017-10-04 16:21:19 +02001224 tcpcheck_main(check);
1225 return SF_ERR_UP;
1226 }
1227
Simon Hormanb00d17a2014-06-13 16:18:16 +09001228 /* prepare a new connection */
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001229 cs = check->cs = cs_new(NULL);
1230 if (!check->cs)
Willy Tarreau00149122017-10-04 18:05:01 +02001231 return SF_ERR_RESOURCE;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001232 conn = cs->conn;
Olivier Houchard26e1a8f2018-09-12 15:15:12 +02001233 /* Maybe there were an older connection we were waiting on */
Willy Tarreau4f6516d2018-12-19 13:59:17 +01001234 check->wait_list.events = 0;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02001235 tasklet_set_tid(check->wait_list.tasklet, tid);
1236
Simon Hormanb00d17a2014-06-13 16:18:16 +09001237
Willy Tarreauca79f592019-07-17 19:04:47 +02001238 if (!sockaddr_alloc(&conn->dst))
1239 return SF_ERR_RESOURCE;
1240
Simon Horman41f58762015-01-30 11:22:56 +09001241 if (is_addr(&check->addr)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001242 /* we'll connect to the check addr specified on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001243 *conn->dst = check->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001244 }
1245 else {
1246 /* we'll connect to the addr on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001247 *conn->dst = s->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001248 }
1249
Alexander Liu2a54bb72019-05-22 19:44:48 +08001250 if (s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1251 conn->send_proxy_ofs = 1;
1252 conn->flags |= CO_FL_SOCKS4;
1253 }
1254
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001255 proto = protocol_by_family(conn->dst->ss_family);
Olivier Houchard6377a002017-12-01 22:04:05 +01001256 conn->target = &s->obj_type;
1257
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001258 if ((conn->dst->ss_family == AF_INET) || (conn->dst->ss_family == AF_INET6)) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001259 int i = 0;
1260
1261 i = srv_check_healthcheck_port(check);
Olivier Houchard6377a002017-12-01 22:04:05 +01001262 if (i == 0)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001263 return SF_ERR_CHK_PORT;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001264
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001265 set_host_port(conn->dst, i);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001266 }
1267
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001268 /* no client address */
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001269
Willy Tarreaube373152018-09-06 11:45:30 +02001270 conn_prepare(conn, proto, check->xprt);
Olivier Houchardf67be932019-01-29 15:47:43 +01001271 if (conn_install_mux(conn, &mux_pt_ops, cs, s->proxy, NULL) < 0)
1272 return SF_ERR_RESOURCE;
Willy Tarreaube373152018-09-06 11:45:30 +02001273 cs_attach(cs, check, &check_conn_cb);
1274
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001275 /* only plain tcp check supports quick ACK */
1276 connflags |= (check->type ? CONNECT_HAS_DATA : CONNECT_DELACK_ALWAYS);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001277
Willy Tarreaue7dff022015-04-03 01:14:29 +02001278 ret = SF_ERR_INTERNAL;
Olivier Houchardb68fda42017-08-04 18:39:01 +02001279 if (proto && proto->connect)
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001280 ret = proto->connect(conn, connflags);
Willy Tarreau16257f62017-11-02 15:45:00 +01001281
Willy Tarreau16257f62017-11-02 15:45:00 +01001282
Olivier Houchard9130a962017-10-17 17:33:43 +02001283#ifdef USE_OPENSSL
Olivier Houcharda48437b2019-01-29 16:37:52 +01001284 if (ret == SF_ERR_NONE) {
1285 if (s->check.sni)
1286 ssl_sock_set_servername(conn, s->check.sni);
1287 if (s->check.alpn_str)
1288 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str,
1289 s->check.alpn_len);
1290 }
Olivier Houchard9130a962017-10-17 17:33:43 +02001291#endif
Willy Tarreauf4949772017-05-06 08:45:28 +02001292 if (s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001293 conn->send_proxy_ofs = 1;
1294 conn->flags |= CO_FL_SEND_PROXY;
Olivier Houchard37d78972019-12-30 15:13:42 +01001295 }
1296 if (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4) &&
1297 conn_ctrl_ready(conn)) {
Olivier Houchardfe50bfb2019-05-27 12:09:19 +02001298 if (xprt_add_hs(conn) < 0)
1299 ret = SF_ERR_RESOURCE;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001300 }
1301
1302 return ret;
1303}
1304
Simon Horman98637e52014-06-20 12:30:16 +09001305static struct list pid_list = LIST_HEAD_INIT(pid_list);
Willy Tarreaubafbe012017-11-24 17:34:44 +01001306static struct pool_head *pool_head_pid_list;
Willy Tarreau86abe442018-11-25 20:12:18 +01001307__decl_spinlock(pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001308
1309void block_sigchld(void)
1310{
1311 sigset_t set;
1312 sigemptyset(&set);
1313 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001314 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001315}
1316
1317void unblock_sigchld(void)
1318{
1319 sigset_t set;
1320 sigemptyset(&set);
Willy Tarreauebc92442016-06-21 17:29:46 +02001321 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001322 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001323}
1324
Simon Horman98637e52014-06-20 12:30:16 +09001325static struct pid_list *pid_list_add(pid_t pid, struct task *t)
1326{
1327 struct pid_list *elem;
1328 struct check *check = t->context;
1329
Willy Tarreaubafbe012017-11-24 17:34:44 +01001330 elem = pool_alloc(pool_head_pid_list);
Simon Horman98637e52014-06-20 12:30:16 +09001331 if (!elem)
1332 return NULL;
1333 elem->pid = pid;
1334 elem->t = t;
1335 elem->exited = 0;
1336 check->curpid = elem;
1337 LIST_INIT(&elem->list);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001338
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001339 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001340 LIST_ADD(&pid_list, &elem->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001341 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001342
Simon Horman98637e52014-06-20 12:30:16 +09001343 return elem;
1344}
1345
Simon Horman98637e52014-06-20 12:30:16 +09001346static void pid_list_del(struct pid_list *elem)
1347{
1348 struct check *check;
1349
1350 if (!elem)
1351 return;
1352
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001353 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001354 LIST_DEL(&elem->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001355 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001356
Simon Horman98637e52014-06-20 12:30:16 +09001357 if (!elem->exited)
1358 kill(elem->pid, SIGTERM);
1359
1360 check = elem->t->context;
1361 check->curpid = NULL;
Willy Tarreaubafbe012017-11-24 17:34:44 +01001362 pool_free(pool_head_pid_list, elem);
Simon Horman98637e52014-06-20 12:30:16 +09001363}
1364
1365/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
1366static void pid_list_expire(pid_t pid, int status)
1367{
1368 struct pid_list *elem;
1369
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001370 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001371 list_for_each_entry(elem, &pid_list, list) {
1372 if (elem->pid == pid) {
1373 elem->t->expire = now_ms;
1374 elem->status = status;
1375 elem->exited = 1;
Cyril Bonté9dbcfab2014-08-07 01:55:39 +02001376 task_wakeup(elem->t, TASK_WOKEN_IO);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001377 break;
Simon Horman98637e52014-06-20 12:30:16 +09001378 }
1379 }
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001380 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001381}
1382
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001383static void sigchld_handler(struct sig_handler *sh)
Simon Horman98637e52014-06-20 12:30:16 +09001384{
1385 pid_t pid;
1386 int status;
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001387
Simon Horman98637e52014-06-20 12:30:16 +09001388 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
1389 pid_list_expire(pid, status);
1390}
1391
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001392static int init_pid_list(void)
1393{
Willy Tarreaubafbe012017-11-24 17:34:44 +01001394 if (pool_head_pid_list != NULL)
Simon Horman98637e52014-06-20 12:30:16 +09001395 /* Nothing to do */
1396 return 0;
1397
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001398 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001399 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
1400 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001401 return 1;
1402 }
1403
Willy Tarreaubafbe012017-11-24 17:34:44 +01001404 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
1405 if (pool_head_pid_list == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001406 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
1407 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001408 return 1;
1409 }
1410
1411 return 0;
1412}
1413
Cyril Bontéac92a062014-12-27 22:28:38 +01001414/* helper macro to set an environment variable and jump to a specific label on failure. */
1415#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001416
1417/*
Cyril Bontéac92a062014-12-27 22:28:38 +01001418 * helper function to allocate enough memory to store an environment variable.
1419 * It will also check that the environment variable is updatable, and silently
1420 * fail if not.
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001421 */
Cyril Bontéac92a062014-12-27 22:28:38 +01001422static int extchk_setenv(struct check *check, int idx, const char *value)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001423{
1424 int len, ret;
Cyril Bontéac92a062014-12-27 22:28:38 +01001425 char *envname;
1426 int vmaxlen;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001427
Cyril Bontéac92a062014-12-27 22:28:38 +01001428 if (idx < 0 || idx >= EXTCHK_SIZE) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001429 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
Cyril Bontéac92a062014-12-27 22:28:38 +01001430 return 1;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001431 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001432
1433 envname = extcheck_envs[idx].name;
1434 vmaxlen = extcheck_envs[idx].vmaxlen;
1435
1436 /* Check if the environment variable is already set, and silently reject
1437 * the update if this one is not updatable. */
1438 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
1439 return 0;
1440
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001441 /* Instead of sending NOT_USED, sending an empty value is preferable */
1442 if (strcmp(value, "NOT_USED") == 0) {
1443 value = "";
1444 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001445
1446 len = strlen(envname) + 1;
1447 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
1448 len += strlen(value);
1449 else
1450 len += vmaxlen;
1451
1452 if (!check->envp[idx])
1453 check->envp[idx] = malloc(len + 1);
1454
1455 if (!check->envp[idx]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001456 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001457 return 1;
1458 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001459 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001460 if (ret < 0) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001461 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001462 return 1;
1463 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001464 else if (ret > len) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001465 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001466 return 1;
1467 }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001468 return 0;
1469}
Simon Horman98637e52014-06-20 12:30:16 +09001470
1471static int prepare_external_check(struct check *check)
1472{
1473 struct server *s = check->server;
1474 struct proxy *px = s->proxy;
1475 struct listener *listener = NULL, *l;
1476 int i;
Simon Horman98637e52014-06-20 12:30:16 +09001477 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001478 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001479
1480 list_for_each_entry(l, &px->conf.listeners, by_fe)
1481 /* Use the first INET, INET6 or UNIX listener */
1482 if (l->addr.ss_family == AF_INET ||
1483 l->addr.ss_family == AF_INET6 ||
1484 l->addr.ss_family == AF_UNIX) {
1485 listener = l;
1486 break;
1487 }
1488
Simon Horman98637e52014-06-20 12:30:16 +09001489 check->curpid = NULL;
Cyril Bontéac92a062014-12-27 22:28:38 +01001490 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
1491 if (!check->envp) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001492 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
Cyril Bontéac92a062014-12-27 22:28:38 +01001493 goto err;
1494 }
Simon Horman98637e52014-06-20 12:30:16 +09001495
Cyril Bontéac92a062014-12-27 22:28:38 +01001496 check->argv = calloc(6, sizeof(char *));
1497 if (!check->argv) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001498 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001499 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001500 }
Simon Horman98637e52014-06-20 12:30:16 +09001501
1502 check->argv[0] = px->check_command;
1503
Cyril Bonté777be862014-12-02 21:21:35 +01001504 if (!listener) {
1505 check->argv[1] = strdup("NOT_USED");
1506 check->argv[2] = strdup("NOT_USED");
1507 }
1508 else if (listener->addr.ss_family == AF_INET ||
Simon Horman98637e52014-06-20 12:30:16 +09001509 listener->addr.ss_family == AF_INET6) {
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001510 addr_to_str(&listener->addr, buf, sizeof(buf));
1511 check->argv[1] = strdup(buf);
1512 port_to_str(&listener->addr, buf, sizeof(buf));
1513 check->argv[2] = strdup(buf);
Cyril Bonté777be862014-12-02 21:21:35 +01001514 }
1515 else if (listener->addr.ss_family == AF_UNIX) {
Simon Horman98637e52014-06-20 12:30:16 +09001516 const struct sockaddr_un *un;
1517
1518 un = (struct sockaddr_un *)&listener->addr;
1519 check->argv[1] = strdup(un->sun_path);
1520 check->argv[2] = strdup("NOT_USED");
Cyril Bonté777be862014-12-02 21:21:35 +01001521 }
1522 else {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001523 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001524 goto err;
1525 }
1526
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001527 if (!check->argv[1] || !check->argv[2]) {
1528 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1529 goto err;
1530 }
1531
1532 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
1533 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
1534 if (!check->argv[3] || !check->argv[4]) {
1535 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1536 goto err;
1537 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001538
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001539 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
Willy Tarreau04276f32017-01-06 17:41:29 +01001540 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001541 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Simon Horman98637e52014-06-20 12:30:16 +09001542
Cyril Bontéac92a062014-12-27 22:28:38 +01001543 for (i = 0; i < 5; i++) {
1544 if (!check->argv[i]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001545 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001546 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001547 }
1548 }
Simon Horman98637e52014-06-20 12:30:16 +09001549
Cyril Bontéac92a062014-12-27 22:28:38 +01001550 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001551 /* Add proxy environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001552 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
1553 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
1554 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
1555 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001556 /* Add server environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001557 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
1558 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
1559 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
1560 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
1561 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
1562 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
1563
1564 /* Ensure that we don't leave any hole in check->envp */
1565 for (i = 0; i < EXTCHK_SIZE; i++)
1566 if (!check->envp[i])
1567 EXTCHK_SETENV(check, i, "", err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001568
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001569 return 1;
Simon Horman98637e52014-06-20 12:30:16 +09001570err:
1571 if (check->envp) {
Cyril Bontéac92a062014-12-27 22:28:38 +01001572 for (i = 0; i < EXTCHK_SIZE; i++)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001573 free(check->envp[i]);
Simon Horman98637e52014-06-20 12:30:16 +09001574 free(check->envp);
1575 check->envp = NULL;
1576 }
1577
1578 if (check->argv) {
1579 for (i = 1; i < 5; i++)
1580 free(check->argv[i]);
1581 free(check->argv);
1582 check->argv = NULL;
1583 }
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001584 return 0;
Simon Horman98637e52014-06-20 12:30:16 +09001585}
1586
Simon Hormanb00d17a2014-06-13 16:18:16 +09001587/*
Simon Horman98637e52014-06-20 12:30:16 +09001588 * establish a server health-check that makes use of a process.
1589 *
1590 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001591 * - SF_ERR_NONE if everything's OK
Willy Tarreaue7dff022015-04-03 01:14:29 +02001592 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
Tim Düsterhus4896c442016-11-29 02:15:19 +01001593 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Horman98637e52014-06-20 12:30:16 +09001594 *
1595 * Blocks and then unblocks SIGCHLD
1596 */
1597static int connect_proc_chk(struct task *t)
1598{
Cyril Bontéac92a062014-12-27 22:28:38 +01001599 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001600 struct check *check = t->context;
1601 struct server *s = check->server;
1602 struct proxy *px = s->proxy;
1603 int status;
1604 pid_t pid;
1605
Willy Tarreaue7dff022015-04-03 01:14:29 +02001606 status = SF_ERR_RESOURCE;
Simon Horman98637e52014-06-20 12:30:16 +09001607
1608 block_sigchld();
1609
1610 pid = fork();
1611 if (pid < 0) {
Willy Tarreaud96f1122019-12-03 07:07:36 +01001612 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
1613 (global.tune.options & GTUNE_INSECURE_FORK) ?
1614 "" : " (likely caused by missing 'insecure-fork-wanted')",
Christopher Faulet767a84b2017-11-24 16:50:31 +01001615 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001616 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1617 goto out;
1618 }
1619 if (pid == 0) {
1620 /* Child */
1621 extern char **environ;
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001622 struct rlimit limit;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001623 int fd;
1624
1625 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
1626 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
1627
Willy Tarreau2555ccf2019-02-21 22:22:06 +01001628 my_closefrom(fd);
Willy Tarreaub7b24782016-06-21 15:32:29 +02001629
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001630 /* restore the initial FD limits */
1631 limit.rlim_cur = rlim_fd_cur_at_boot;
1632 limit.rlim_max = rlim_fd_max_at_boot;
1633 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
1634 getrlimit(RLIMIT_NOFILE, &limit);
1635 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
1636 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
1637 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
1638 }
1639
Simon Horman98637e52014-06-20 12:30:16 +09001640 environ = check->envp;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001641
1642 /* Update some environment variables and command args: curconn, server addr and server port */
Cyril Bontéac92a062014-12-27 22:28:38 +01001643 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001644
1645 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
1646 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
1647
1648 *check->argv[4] = 0;
1649 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1650 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
1651 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
1652
Willy Tarreau2df8cad2019-07-01 07:51:29 +02001653 haproxy_unblock_signals();
Simon Horman98637e52014-06-20 12:30:16 +09001654 execvp(px->check_command, check->argv);
Christopher Faulet767a84b2017-11-24 16:50:31 +01001655 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
1656 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001657 exit(-1);
1658 }
1659
1660 /* Parent */
1661 if (check->result == CHK_RES_UNKNOWN) {
1662 if (pid_list_add(pid, t) != NULL) {
1663 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1664
1665 if (px->timeout.check && px->timeout.connect) {
1666 int t_con = tick_add(now_ms, px->timeout.connect);
1667 t->expire = tick_first(t->expire, t_con);
1668 }
Willy Tarreaue7dff022015-04-03 01:14:29 +02001669 status = SF_ERR_NONE;
Simon Horman98637e52014-06-20 12:30:16 +09001670 goto out;
1671 }
1672 else {
1673 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1674 }
1675 kill(pid, SIGTERM); /* process creation error */
1676 }
1677 else
1678 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1679
1680out:
1681 unblock_sigchld();
1682 return status;
1683}
1684
1685/*
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001686 * manages a server health-check that uses an external process. Returns
Willy Tarreaubaaee002006-06-26 02:48:02 +02001687 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001688 *
1689 * Please do NOT place any return statement in this function and only leave
1690 * via the out_unlock label.
Willy Tarreaubaaee002006-06-26 02:48:02 +02001691 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001692static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09001693{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001694 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09001695 struct server *s = check->server;
Simon Horman98637e52014-06-20 12:30:16 +09001696 int rv;
1697 int ret;
1698 int expired = tick_is_expired(t->expire, now_ms);
1699
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001700 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09001701 if (!(check->state & CHK_ST_INPROGRESS)) {
1702 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001703 if (!expired) /* woke up too early */
1704 goto out_unlock;
Simon Horman98637e52014-06-20 12:30:16 +09001705
1706 /* we don't send any health-checks when the proxy is
1707 * stopped, the server should not be checked or the check
1708 * is disabled.
1709 */
1710 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
1711 s->proxy->state == PR_STSTOPPED)
1712 goto reschedule;
1713
1714 /* we'll initiate a new check */
1715 set_server_check_status(check, HCHK_STATUS_START, NULL);
1716
1717 check->state |= CHK_ST_INPROGRESS;
1718
Simon Hormandbf70192015-01-30 11:22:53 +09001719 ret = connect_proc_chk(t);
Willy Tarreaud7c3fbd2017-10-04 15:19:26 +02001720 if (ret == SF_ERR_NONE) {
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001721 /* the process was forked, we allow up to min(inter,
1722 * timeout.connect) for it to report its status, but
1723 * only when timeout.check is set as it may be to short
1724 * for a full check otherwise.
Simon Horman98637e52014-06-20 12:30:16 +09001725 */
1726 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1727
1728 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
1729 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
1730 t->expire = tick_first(t->expire, t_con);
1731 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02001732 task_set_affinity(t, tid_bit);
Simon Horman98637e52014-06-20 12:30:16 +09001733 goto reschedule;
Simon Horman98637e52014-06-20 12:30:16 +09001734 }
1735
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001736 /* here, we failed to start the check */
Simon Horman98637e52014-06-20 12:30:16 +09001737
1738 check->state &= ~CHK_ST_INPROGRESS;
1739 check_notify_failure(check);
1740
1741 /* we allow up to min(inter, timeout.connect) for a connection
1742 * to establish but only when timeout.check is set
1743 * as it may be to short for a full check otherwise
1744 */
1745 while (tick_is_expired(t->expire, now_ms)) {
1746 int t_con;
1747
1748 t_con = tick_add(t->expire, s->proxy->timeout.connect);
1749 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
1750
1751 if (s->proxy->timeout.check)
1752 t->expire = tick_first(t->expire, t_con);
1753 }
1754 }
1755 else {
1756 /* there was a test running.
1757 * First, let's check whether there was an uncaught error,
1758 * which can happen on connect timeout or error.
1759 */
1760 if (check->result == CHK_RES_UNKNOWN) {
1761 /* good connection is enough for pure TCP check */
1762 struct pid_list *elem = check->curpid;
1763 int status = HCHK_STATUS_UNKNOWN;
1764
1765 if (elem->exited) {
1766 status = elem->status; /* Save in case the process exits between use below */
1767 if (!WIFEXITED(status))
1768 check->code = -1;
1769 else
1770 check->code = WEXITSTATUS(status);
1771 if (!WIFEXITED(status) || WEXITSTATUS(status))
1772 status = HCHK_STATUS_PROCERR;
1773 else
1774 status = HCHK_STATUS_PROCOK;
1775 } else if (expired) {
1776 status = HCHK_STATUS_PROCTOUT;
Christopher Faulet767a84b2017-11-24 16:50:31 +01001777 ha_warning("kill %d\n", (int)elem->pid);
Simon Horman98637e52014-06-20 12:30:16 +09001778 kill(elem->pid, SIGTERM);
1779 }
1780 set_server_check_status(check, status, NULL);
1781 }
1782
1783 if (check->result == CHK_RES_FAILED) {
1784 /* a failure or timeout detected */
1785 check_notify_failure(check);
1786 }
1787 else if (check->result == CHK_RES_CONDPASS) {
1788 /* check is OK but asks for stopping mode */
1789 check_notify_stopping(check);
1790 }
1791 else if (check->result == CHK_RES_PASSED) {
1792 /* a success was detected */
1793 check_notify_success(check);
1794 }
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001795 task_set_affinity(t, 1);
Simon Horman98637e52014-06-20 12:30:16 +09001796 check->state &= ~CHK_ST_INPROGRESS;
1797
1798 pid_list_del(check->curpid);
1799
1800 rv = 0;
1801 if (global.spread_checks > 0) {
1802 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01001803 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Simon Horman98637e52014-06-20 12:30:16 +09001804 }
1805 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
1806 }
1807
1808 reschedule:
1809 while (tick_is_expired(t->expire, now_ms))
1810 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001811
1812 out_unlock:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001813 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09001814 return t;
1815}
1816
1817/*
1818 * manages a server health-check that uses a connection. Returns
1819 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001820 *
1821 * Please do NOT place any return statement in this function and only leave
1822 * via the out_unlock label.
Simon Horman98637e52014-06-20 12:30:16 +09001823 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001824static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001825{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001826 struct check *check = context;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001827 struct proxy *proxy = check->proxy;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001828 struct conn_stream *cs = check->cs;
1829 struct connection *conn = cs_conn(cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001830 int rv;
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001831 int ret;
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01001832 int expired = tick_is_expired(t->expire, now_ms);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001833
Olivier Houchard0923fa42019-01-11 18:43:04 +01001834 if (check->server)
1835 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau2c115e52013-12-11 19:41:16 +01001836 if (!(check->state & CHK_ST_INPROGRESS)) {
Willy Tarreau5a78f362012-11-23 12:47:05 +01001837 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001838 if (!expired) /* woke up too early */
1839 goto out_unlock;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001840
Simon Horman671b6f02013-11-25 10:46:39 +09001841 /* we don't send any health-checks when the proxy is
1842 * stopped, the server should not be checked or the check
1843 * is disabled.
Willy Tarreaubaaee002006-06-26 02:48:02 +02001844 */
Willy Tarreau0d924cc2013-12-11 21:26:24 +01001845 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001846 proxy->state == PR_STSTOPPED)
Willy Tarreau5a78f362012-11-23 12:47:05 +01001847 goto reschedule;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001848
1849 /* we'll initiate a new check */
Simon Horman4a741432013-02-23 15:35:38 +09001850 set_server_check_status(check, HCHK_STATUS_START, NULL);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001851
Willy Tarreau2c115e52013-12-11 19:41:16 +01001852 check->state |= CHK_ST_INPROGRESS;
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001853 b_reset(&check->bi);
1854 b_reset(&check->bo);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001855
Olivier Houchardaebeff72019-11-29 16:18:51 +01001856 task_set_affinity(t, tid_bit);
Simon Hormandbf70192015-01-30 11:22:53 +09001857 ret = connect_conn_chk(t);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001858 cs = check->cs;
1859 conn = cs_conn(cs);
Willy Tarreau00149122017-10-04 18:05:01 +02001860
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001861 switch (ret) {
Willy Tarreaue7dff022015-04-03 01:14:29 +02001862 case SF_ERR_UP:
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001863 goto out_unlock;
1864
Willy Tarreaue7dff022015-04-03 01:14:29 +02001865 case SF_ERR_NONE:
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001866 /* we allow up to min(inter, timeout.connect) for a connection
1867 * to establish but only when timeout.check is set
1868 * as it may be to short for a full check otherwise
1869 */
Simon Horman4a741432013-02-23 15:35:38 +09001870 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001871 if (proxy->timeout.check && proxy->timeout.connect) {
1872 int t_con = tick_add(now_ms, proxy->timeout.connect);
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001873 t->expire = tick_first(t->expire, t_con);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001874 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01001875
Willy Tarreaucc705a62019-09-05 17:51:30 +02001876 if (check->type) {
1877 /* send the request if we have one. We avoid receiving
1878 * if not connected, unless we didn't subscribe for
1879 * sending since otherwise we won't be woken up.
1880 */
1881 __event_srv_chk_w(cs);
Willy Tarreau911db9b2020-01-23 16:27:54 +01001882 if (!(conn->flags & CO_FL_WAIT_XPRT) ||
Willy Tarreauc5940392019-09-05 17:38:40 +02001883 !(check->wait_list.events & SUB_RETRY_SEND))
1884 __event_srv_chk_r(cs);
Willy Tarreaucc705a62019-09-05 17:51:30 +02001885 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01001886
Willy Tarreau5a78f362012-11-23 12:47:05 +01001887 goto reschedule;
1888
Willy Tarreaue7dff022015-04-03 01:14:29 +02001889 case SF_ERR_SRVTO: /* ETIMEDOUT */
1890 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
Willy Tarreau00149122017-10-04 18:05:01 +02001891 if (conn)
1892 conn->flags |= CO_FL_ERROR;
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001893 chk_report_conn_err(check, errno, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01001894 break;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001895 /* should share same code than cases below */
1896 case SF_ERR_CHK_PORT:
1897 check->state |= CHK_ST_PORT_MISS;
Willy Tarreaue7dff022015-04-03 01:14:29 +02001898 case SF_ERR_PRXCOND:
1899 case SF_ERR_RESOURCE:
1900 case SF_ERR_INTERNAL:
Willy Tarreau00149122017-10-04 18:05:01 +02001901 if (conn)
1902 conn->flags |= CO_FL_ERROR;
1903 chk_report_conn_err(check, conn ? 0 : ENOMEM, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01001904 break;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001905 }
1906
Willy Tarreau5a78f362012-11-23 12:47:05 +01001907 /* here, we have seen a synchronous error, no fd was allocated */
Olivier Houchardaebeff72019-11-29 16:18:51 +01001908 task_set_affinity(t, MAX_THREADS_MASK);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001909 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02001910 if (check->wait_list.events)
1911 cs->conn->xprt->unsubscribe(cs->conn,
1912 cs->conn->xprt_ctx,
1913 check->wait_list.events,
1914 &check->wait_list);
1915 /* We may have been scheduled to run, and the
1916 * I/O handler expects to have a cs, so remove
1917 * the tasklet
1918 */
Willy Tarreau86eded62019-06-14 14:47:49 +02001919 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001920 cs_destroy(cs);
1921 cs = check->cs = NULL;
1922 conn = NULL;
Olivier Houchard390485a2017-10-24 19:03:30 +02001923 }
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001924
Willy Tarreau2c115e52013-12-11 19:41:16 +01001925 check->state &= ~CHK_ST_INPROGRESS;
Willy Tarreau4eec5472014-05-20 22:32:27 +02001926 check_notify_failure(check);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001927
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001928 /* we allow up to min(inter, timeout.connect) for a connection
1929 * to establish but only when timeout.check is set
1930 * as it may be to short for a full check otherwise
1931 */
Willy Tarreau0c303ee2008-07-07 00:09:58 +02001932 while (tick_is_expired(t->expire, now_ms)) {
1933 int t_con;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001934
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001935 t_con = tick_add(t->expire, proxy->timeout.connect);
Simon Horman4a741432013-02-23 15:35:38 +09001936 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001937 if (proxy->timeout.check)
Willy Tarreau0c303ee2008-07-07 00:09:58 +02001938 t->expire = tick_first(t->expire, t_con);
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001939 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02001940 }
1941 else {
Willy Tarreauf1503172012-09-28 19:39:36 +02001942 /* there was a test running.
1943 * First, let's check whether there was an uncaught error,
1944 * which can happen on connect timeout or error.
1945 */
Simon Hormanccaabcd2014-06-20 12:29:47 +09001946 if (check->result == CHK_RES_UNKNOWN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001947 /* good connection is enough for pure TCP check */
Willy Tarreau911db9b2020-01-23 16:27:54 +01001948 if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Christopher Fauletf61f33a2020-03-27 18:55:49 +01001949 if (check->use_ssl == 1)
Simon Horman4a741432013-02-23 15:35:38 +09001950 set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
Willy Tarreauf1503172012-09-28 19:39:36 +02001951 else
Simon Horman4a741432013-02-23 15:35:38 +09001952 set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01001953 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +02001954 else if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001955 chk_report_conn_err(check, 0, expired);
Willy Tarreauf1503172012-09-28 19:39:36 +02001956 }
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01001957 else
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001958 goto out_unlock; /* timeout not reached, wait again */
Willy Tarreauf1503172012-09-28 19:39:36 +02001959 }
1960
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01001961 /* check complete or aborted */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001962
1963 check->current_step = NULL;
1964 if (check->sess != NULL) {
1965 session_free(check->sess);
1966 check->sess = NULL;
1967 }
1968
Willy Tarreau00149122017-10-04 18:05:01 +02001969 if (conn && conn->xprt) {
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001970 /* The check was aborted and the connection was not yet closed.
1971 * This can happen upon timeout, or when an external event such
1972 * as a failed response coupled with "observe layer7" caused the
1973 * server state to be suddenly changed.
1974 */
Willy Tarreaud85c4852015-03-13 00:40:28 +01001975 conn_sock_drain(conn);
Willy Tarreaua553ae92017-10-05 18:52:17 +02001976 cs_close(cs);
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001977 }
1978
Willy Tarreauac59f362017-10-08 11:10:19 +02001979 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02001980 if (check->wait_list.events)
1981 cs->conn->xprt->unsubscribe(cs->conn,
1982 cs->conn->xprt_ctx,
1983 check->wait_list.events,
1984 &check->wait_list);
1985 /* We may have been scheduled to run, and the
Willy Tarreau86eded62019-06-14 14:47:49 +02001986 * I/O handler expects to have a cs, so remove
1987 * the tasklet
1988 */
1989 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001990 cs_destroy(cs);
1991 cs = check->cs = NULL;
1992 conn = NULL;
Willy Tarreau00149122017-10-04 18:05:01 +02001993 }
1994
Olivier Houchard0923fa42019-01-11 18:43:04 +01001995 if (check->server) {
1996 if (check->result == CHK_RES_FAILED) {
1997 /* a failure or timeout detected */
1998 check_notify_failure(check);
1999 }
2000 else if (check->result == CHK_RES_CONDPASS) {
2001 /* check is OK but asks for stopping mode */
2002 check_notify_stopping(check);
2003 }
2004 else if (check->result == CHK_RES_PASSED) {
2005 /* a success was detected */
2006 check_notify_success(check);
2007 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02002008 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02002009 task_set_affinity(t, MAX_THREADS_MASK);
Willy Tarreau2c115e52013-12-11 19:41:16 +01002010 check->state &= ~CHK_ST_INPROGRESS;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002011
Olivier Houchard0923fa42019-01-11 18:43:04 +01002012 if (check->server) {
2013 rv = 0;
2014 if (global.spread_checks > 0) {
2015 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01002016 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Olivier Houchard0923fa42019-01-11 18:43:04 +01002017 }
2018 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Willy Tarreaubaaee002006-06-26 02:48:02 +02002019 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02002020 }
Willy Tarreau5a78f362012-11-23 12:47:05 +01002021
2022 reschedule:
2023 while (tick_is_expired(t->expire, now_ms))
Simon Horman4a741432013-02-23 15:35:38 +09002024 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01002025 out_unlock:
Olivier Houchard0923fa42019-01-11 18:43:04 +01002026 if (check->server)
2027 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau26c25062009-03-08 09:38:41 +01002028 return t;
Willy Tarreaubaaee002006-06-26 02:48:02 +02002029}
2030
Simon Horman98637e52014-06-20 12:30:16 +09002031/*
2032 * manages a server health-check. Returns
2033 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
2034 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02002035static struct task *process_chk(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09002036{
Olivier Houchard9f6af332018-05-25 14:04:04 +02002037 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09002038
2039 if (check->type == PR_O2_EXT_CHK)
Olivier Houchard9f6af332018-05-25 14:04:04 +02002040 return process_chk_proc(t, context, state);
2041 return process_chk_conn(t, context, state);
Baptiste Assmanna68ca962015-04-14 01:15:08 +02002042
Simon Horman98637e52014-06-20 12:30:16 +09002043}
2044
Simon Horman5c942422013-11-25 10:46:32 +09002045static int start_check_task(struct check *check, int mininter,
2046 int nbcheck, int srvpos)
2047{
2048 struct task *t;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002049 unsigned long thread_mask = MAX_THREADS_MASK;
2050
2051 if (check->type == PR_O2_EXT_CHK)
2052 thread_mask = 1;
2053
Simon Horman5c942422013-11-25 10:46:32 +09002054 /* task for the check */
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002055 if ((t = task_new(thread_mask)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002056 ha_alert("Starting [%s:%s] check: out of memory.\n",
2057 check->server->proxy->id, check->server->id);
Simon Horman5c942422013-11-25 10:46:32 +09002058 return 0;
2059 }
2060
2061 check->task = t;
2062 t->process = process_chk;
2063 t->context = check;
2064
Willy Tarreau1746eec2014-04-25 10:46:47 +02002065 if (mininter < srv_getinter(check))
2066 mininter = srv_getinter(check);
2067
2068 if (global.max_spread_checks && mininter > global.max_spread_checks)
2069 mininter = global.max_spread_checks;
2070
Simon Horman5c942422013-11-25 10:46:32 +09002071 /* check this every ms */
Willy Tarreau1746eec2014-04-25 10:46:47 +02002072 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
Simon Horman5c942422013-11-25 10:46:32 +09002073 check->start = now;
2074 task_queue(t);
2075
2076 return 1;
2077}
2078
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002079/*
2080 * Start health-check.
Willy Tarreau865c5142016-12-21 20:04:48 +01002081 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002082 */
Willy Tarreau865c5142016-12-21 20:04:48 +01002083static int start_checks()
2084{
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002085
2086 struct proxy *px;
2087 struct server *s;
2088 struct task *t;
Simon Horman4a741432013-02-23 15:35:38 +09002089 int nbcheck=0, mininter=0, srvpos=0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002090
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002091 /* 0- init the dummy frontend used to create all checks sessions */
2092 init_new_proxy(&checks_fe);
2093 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
2094 checks_fe.mode = PR_MODE_TCP;
2095 checks_fe.maxconn = 0;
2096 checks_fe.conn_retries = CONN_RETRIES;
2097 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
2098 checks_fe.timeout.client = TICK_ETERNITY;
2099
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002100 /* 1- count the checkers to run simultaneously.
2101 * We also determine the minimum interval among all of those which
2102 * have an interval larger than SRV_CHK_INTER_THRES. This interval
2103 * will be used to spread their start-up date. Those which have
Jamie Gloudon801a0a32012-08-25 00:18:33 -04002104 * a shorter interval will start independently and will not dictate
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002105 * too short an interval for all others.
2106 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002107 for (px = proxies_list; px; px = px->next) {
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002108 for (s = px->srv; s; s = s->next) {
Willy Tarreaue7b73482013-11-21 11:50:50 +01002109 if (s->slowstart) {
Emeric Brunc60def82017-09-27 14:59:38 +02002110 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002111 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002112 return ERR_ALERT | ERR_FATAL;
Willy Tarreaue7b73482013-11-21 11:50:50 +01002113 }
2114 /* We need a warmup task that will be called when the server
2115 * state switches from down to up.
2116 */
2117 s->warmup = t;
2118 t->process = server_warmup;
2119 t->context = s;
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002120 /* server can be in this state only because of */
Emeric Brun52a91d32017-08-31 14:41:55 +02002121 if (s->next_state == SRV_ST_STARTING)
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002122 task_schedule(s->warmup, tick_add(now_ms, MS_TO_TICKS(MAX(1000, (now.tv_sec - s->last_change)) / 20)));
Willy Tarreaue7b73482013-11-21 11:50:50 +01002123 }
2124
Willy Tarreaud8514a22013-12-11 21:10:14 +01002125 if (s->check.state & CHK_ST_CONFIGURED) {
2126 nbcheck++;
2127 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
2128 (!mininter || mininter > srv_getinter(&s->check)))
2129 mininter = srv_getinter(&s->check);
2130 }
Willy Tarreau15f39102013-12-11 20:41:18 +01002131
Willy Tarreaud8514a22013-12-11 21:10:14 +01002132 if (s->agent.state & CHK_ST_CONFIGURED) {
2133 nbcheck++;
2134 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
2135 (!mininter || mininter > srv_getinter(&s->agent)))
2136 mininter = srv_getinter(&s->agent);
2137 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002138 }
2139 }
2140
Simon Horman4a741432013-02-23 15:35:38 +09002141 if (!nbcheck)
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002142 return 0;
2143
2144 srand((unsigned)time(NULL));
2145
2146 /*
2147 * 2- start them as far as possible from each others. For this, we will
2148 * start them after their interval set to the min interval divided by
2149 * the number of servers, weighted by the server's position in the list.
2150 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002151 for (px = proxies_list; px; px = px->next) {
Simon Horman98637e52014-06-20 12:30:16 +09002152 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
2153 if (init_pid_list()) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002154 ha_alert("Starting [%s] check: out of memory.\n", px->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002155 return ERR_ALERT | ERR_FATAL;
Simon Horman98637e52014-06-20 12:30:16 +09002156 }
2157 }
2158
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002159 for (s = px->srv; s; s = s->next) {
Simon Hormand60d6912013-11-25 10:46:36 +09002160 /* A task for the main check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002161 if (s->check.state & CHK_ST_CONFIGURED) {
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002162 if (s->check.type == PR_O2_EXT_CHK) {
2163 if (!prepare_external_check(&s->check))
Willy Tarreau865c5142016-12-21 20:04:48 +01002164 return ERR_ALERT | ERR_FATAL;
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002165 }
Simon Hormand60d6912013-11-25 10:46:36 +09002166 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
Willy Tarreau865c5142016-12-21 20:04:48 +01002167 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002168 srvpos++;
2169 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002170
Simon Hormand60d6912013-11-25 10:46:36 +09002171 /* A task for a auxiliary agent check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002172 if (s->agent.state & CHK_ST_CONFIGURED) {
Simon Hormand60d6912013-11-25 10:46:36 +09002173 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
Willy Tarreau865c5142016-12-21 20:04:48 +01002174 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002175 }
2176 srvpos++;
2177 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002178 }
2179 }
2180 return 0;
2181}
Willy Tarreaubaaee002006-06-26 02:48:02 +02002182
2183/*
Willy Tarreau5b3a2022012-09-28 15:01:02 +02002184 * Perform content verification check on data in s->check.buffer buffer.
Willy Tarreaubd741542010-03-16 18:46:54 +01002185 * The buffer MUST be terminated by a null byte before calling this function.
2186 * Sets server status appropriately. The caller is responsible for ensuring
2187 * that the buffer contains at least 13 characters. If <done> is zero, we may
2188 * return 0 to indicate that data is required to decide of a match.
2189 */
2190static int httpchk_expect(struct server *s, int done)
2191{
Christopher Faulet1bc04c72017-10-29 20:14:08 +01002192 static THREAD_LOCAL char status_msg[] = "HTTP status check returned code <000>";
Willy Tarreaubd741542010-03-16 18:46:54 +01002193 char status_code[] = "000";
2194 char *contentptr;
2195 int crlf;
2196 int ret;
2197
2198 switch (s->proxy->options2 & PR_O2_EXP_TYPE) {
2199 case PR_O2_EXP_STS:
2200 case PR_O2_EXP_RSTS:
Willy Tarreauc9fa0482018-07-10 17:43:27 +02002201 memcpy(status_code, b_head(&s->check.bi) + 9, 3);
2202 memcpy(status_msg + strlen(status_msg) - 4, b_head(&s->check.bi) + 9, 3);
Willy Tarreaubd741542010-03-16 18:46:54 +01002203
2204 if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STS)
2205 ret = strncmp(s->proxy->expect_str, status_code, 3) == 0;
2206 else
Thierry FOURNIER09af0d62014-06-18 11:35:54 +02002207 ret = regex_exec(s->proxy->expect_regex, status_code);
Willy Tarreaubd741542010-03-16 18:46:54 +01002208
2209 /* we necessarily have the response, so there are no partial failures */
2210 if (s->proxy->options2 & PR_O2_EXP_INV)
2211 ret = !ret;
2212
Simon Horman4a741432013-02-23 15:35:38 +09002213 set_server_check_status(&s->check, ret ? HCHK_STATUS_L7OKD : HCHK_STATUS_L7STS, status_msg);
Willy Tarreaubd741542010-03-16 18:46:54 +01002214 break;
2215
2216 case PR_O2_EXP_STR:
2217 case PR_O2_EXP_RSTR:
2218 /* very simple response parser: ignore CR and only count consecutive LFs,
2219 * stop with contentptr pointing to first char after the double CRLF or
2220 * to '\0' if crlf < 2.
2221 */
2222 crlf = 0;
Willy Tarreauc9fa0482018-07-10 17:43:27 +02002223 for (contentptr = b_head(&s->check.bi); *contentptr; contentptr++) {
Willy Tarreaubd741542010-03-16 18:46:54 +01002224 if (crlf >= 2)
2225 break;
2226 if (*contentptr == '\r')
2227 continue;
2228 else if (*contentptr == '\n')
2229 crlf++;
2230 else
2231 crlf = 0;
2232 }
2233
2234 /* Check that response contains a body... */
2235 if (crlf < 2) {
2236 if (!done)
2237 return 0;
2238
Simon Horman4a741432013-02-23 15:35:38 +09002239 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002240 "HTTP content check could not find a response body");
2241 return 1;
2242 }
2243
2244 /* Check that response body is not empty... */
2245 if (*contentptr == '\0') {
Willy Tarreaua164fb52011-04-13 09:32:41 +02002246 if (!done)
2247 return 0;
2248
Simon Horman4a741432013-02-23 15:35:38 +09002249 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002250 "HTTP content check found empty response body");
2251 return 1;
2252 }
2253
2254 /* Check the response content against the supplied string
2255 * or regex... */
2256 if ((s->proxy->options2 & PR_O2_EXP_TYPE) == PR_O2_EXP_STR)
2257 ret = strstr(contentptr, s->proxy->expect_str) != NULL;
2258 else
Thierry FOURNIER09af0d62014-06-18 11:35:54 +02002259 ret = regex_exec(s->proxy->expect_regex, contentptr);
Willy Tarreaubd741542010-03-16 18:46:54 +01002260
2261 /* if we don't match, we may need to wait more */
2262 if (!ret && !done)
2263 return 0;
2264
2265 if (ret) {
2266 /* content matched */
2267 if (s->proxy->options2 & PR_O2_EXP_INV)
Simon Horman4a741432013-02-23 15:35:38 +09002268 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002269 "HTTP check matched unwanted content");
2270 else
Simon Horman4a741432013-02-23 15:35:38 +09002271 set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
Willy Tarreaubd741542010-03-16 18:46:54 +01002272 "HTTP content check matched");
2273 }
2274 else {
2275 if (s->proxy->options2 & PR_O2_EXP_INV)
Simon Horman4a741432013-02-23 15:35:38 +09002276 set_server_check_status(&s->check, HCHK_STATUS_L7OKD,
Willy Tarreaubd741542010-03-16 18:46:54 +01002277 "HTTP check did not match unwanted content");
2278 else
Simon Horman4a741432013-02-23 15:35:38 +09002279 set_server_check_status(&s->check, HCHK_STATUS_L7RSP,
Willy Tarreaubd741542010-03-16 18:46:54 +01002280 "HTTP content check did not match");
2281 }
2282 break;
2283 }
2284 return 1;
2285}
2286
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002287/*
2288 * return the id of a step in a send/expect session
2289 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002290static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002291{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002292 if (!rule)
2293 rule = check->current_step;
Willy Tarreau213c6782014-10-02 14:51:02 +02002294
Christopher Faulet3c29aa62020-03-24 13:31:19 +01002295 /* no last started step => first step */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002296 if (!rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002297 return 1;
2298
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002299 /* last step is the first implicit connect */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002300 if (rule->index == 0 &&
2301 rule->action == TCPCHK_ACT_CONNECT &&
Christopher Fauletbb591a12020-04-01 16:52:17 +02002302 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002303 return 0;
2304
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002305 return rule->index + 1;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002306}
2307
Christopher Faulet206368d2020-04-03 14:51:06 +02002308static void tcpcheck_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2309 int match, struct ist info)
2310{
2311 struct sample *smp;
2312
2313 if (istlen(info)) {
2314 chunk_strncat(msg, info.ptr, info.len);
2315 goto comment;
2316 }
2317 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
2318 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
2319 goto comment;
2320 }
2321
Christopher Faulet799f3a42020-04-07 12:06:14 +02002322 if (check->type == PR_O2_TCPCHK_CHK && (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK))
2323 goto comment;
2324
Christopher Faulet206368d2020-04-03 14:51:06 +02002325 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
2326 switch (rule->expect.type) {
2327 case TCPCHK_EXPECT_STRING:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02002328 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), rule->expect.data.ptr,
2329 tcpcheck_get_step_id(check, rule));
Christopher Faulet206368d2020-04-03 14:51:06 +02002330 break;
2331 case TCPCHK_EXPECT_BINARY:
2332 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
2333 break;
2334 case TCPCHK_EXPECT_REGEX:
2335 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
2336 break;
2337 case TCPCHK_EXPECT_REGEX_BINARY:
2338 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
2339
2340 /* If references to the matched text were made, divide the
2341 * offsets by 2 to match offset of the original response buffer.
2342 */
2343 if (rule->expect.with_capture) {
2344 int i;
2345
2346 for (i = 1; i < MAX_MATCH && pmatch[i].rm_so != -1; i++) {
2347 pmatch[i].rm_so /= 2; /* at first matched char. */
2348 pmatch[i].rm_eo /= 2; /* at last matched char. */
2349 }
2350 }
2351 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02002352 case TCPCHK_EXPECT_CUSTOM:
2353 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
2354 break;
Christopher Faulet206368d2020-04-03 14:51:06 +02002355 case TCPCHK_EXPECT_UNDEF:
2356 /* Should never happen. */
2357 return;
2358 }
2359
2360 comment:
2361 if (rule->comment) {
2362 chunk_strcat(msg, " comment: ");
2363 if (rule->expect.with_capture) {
2364 int ret = exp_replace(b_tail(msg), b_room(msg), b_head(&check->bi), rule->comment, pmatch);
2365 if (ret != -1) /* ignore comment if too large */
2366 msg->data += ret;
2367 }
2368 else
2369 chunk_strcat(msg, rule->comment);
2370 }
2371
2372 if (rule->expect.status_expr) {
2373 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2374 rule->expect.status_expr, SMP_T_SINT);
2375 if (smp)
2376 check->code = smp->data.u.sint;
2377 }
2378
2379 *(b_tail(msg)) = '\0';
2380}
2381
2382static void tcpcheck_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2383 struct ist info)
2384{
2385 struct sample *smp;
2386
2387 if (istlen(info))
2388 chunk_strncat(msg, info.ptr, info.len);
2389 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
2390 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
2391 &rule->expect.onsuccess_fmt);
Christopher Faulet799f3a42020-04-07 12:06:14 +02002392 else if (check->type == PR_O2_TCPCHK_CHK && !(check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK))
Christopher Faulet206368d2020-04-03 14:51:06 +02002393 chunk_strcat(msg, "(tcp-check)");
2394
2395 if (rule->expect.status_expr) {
2396 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2397 rule->expect.status_expr, SMP_T_SINT);
2398 if (smp)
2399 check->code = smp->data.u.sint;
2400 }
2401
2402 *(b_tail(msg)) = '\0';
2403}
2404
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002405static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
2406 unsigned int offset, int last_read)
2407{
2408 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2409 enum healthcheck_status status;
2410 struct buffer *msg = NULL;
2411 struct ist desc = ist(NULL);
2412 unsigned int err = 0, plen = 0;
2413
2414
2415 /* 3 Bytes for the packet length and 1 byte for the sequence id */
2416 if (!last_read && b_data(&check->bi) < offset+4) {
2417 if (!last_read)
2418 goto wait_more_data;
2419
2420 /* invalid length or truncated response */
2421 status = HCHK_STATUS_L7RSP;
2422 goto error;
2423 }
2424
2425 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
2426 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
2427 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
2428
2429 if (b_data(&check->bi) < offset+plen+4) {
2430 if (!last_read)
2431 goto wait_more_data;
2432
2433 /* invalid length or truncated response */
2434 status = HCHK_STATUS_L7RSP;
2435 goto error;
2436 }
2437
2438 if (*b_peek(&check->bi, offset+4) == '\xff') {
2439 /* MySQL Error packet always begin with field_count = 0xff */
2440 status = HCHK_STATUS_L7STS;
2441 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
2442 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
2443 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
2444 goto error;
2445 }
2446
2447 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
2448 /* Not the last rule, continue */
2449 goto out;
2450 }
2451
2452 /* We set the MySQL Version in description for information purpose
2453 * FIXME : it can be cool to use MySQL Version for other purpose,
2454 * like mark as down old MySQL server.
2455 */
Christopher Fauletec07e382020-04-07 14:56:26 +02002456 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002457
2458 out:
2459 free_trash_chunk(msg);
2460 return ret;
2461
2462 error:
2463 ret = TCPCHK_EVAL_STOP;
2464 check->code = err;
2465 msg = alloc_trash_chunk();
2466 if (msg)
2467 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2468 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2469 goto out;
2470
2471 wait_more_data:
2472 ret = TCPCHK_EVAL_WAIT;
2473 goto out;
2474}
2475
2476
2477static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
2478{
2479 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
2480}
2481
2482static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
2483{
2484 unsigned int hslen = 0;
2485
2486 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
2487 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
2488 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
2489
2490 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
2491}
2492
Christopher Faulet1997eca2020-04-03 23:13:50 +02002493static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
2494{
2495 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2496 enum healthcheck_status status;
2497 struct buffer *msg = NULL;
2498 struct ist desc = ist(NULL);
2499 unsigned short msglen = 0;
2500
2501 /* Check if the server speaks LDAP (ASN.1/BER)
2502 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
2503 * http://tools.ietf.org/html/rfc4511
2504 */
2505 /* size of LDAPMessage */
2506 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
2507
2508 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
2509 * messageID: 0x02 0x01 0x01: INTEGER 1
2510 * protocolOp: 0x61: bindResponse
2511 */
2512 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
2513 status = HCHK_STATUS_L7RSP;
2514 desc = ist("Not LDAPv3 protocol");
2515 goto error;
2516 }
2517
2518 /* size of bindResponse */
2519 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
2520
2521 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2522 * ldapResult: 0x0a 0x01: ENUMERATION
2523 */
2524 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
2525 status = HCHK_STATUS_L7RSP;
2526 desc = ist("Not LDAPv3 protocol");
2527 goto error;
2528 }
2529
2530 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2531 * resultCode
2532 */
2533 check->code = *(b_head(&check->bi) + msglen + 9);
2534 if (check->code) {
2535 status = HCHK_STATUS_L7STS;
2536 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
2537 goto error;
2538 }
2539
Christopher Fauletec07e382020-04-07 14:56:26 +02002540 set_server_check_status(check, rule->expect.ok_status, "Success");
Christopher Faulet1997eca2020-04-03 23:13:50 +02002541
2542 out:
2543 free_trash_chunk(msg);
2544 return ret;
2545
2546 error:
2547 ret = TCPCHK_EVAL_STOP;
2548 msg = alloc_trash_chunk();
2549 if (msg)
2550 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2551 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2552 goto out;
2553
2554 wait_more_data:
2555 ret = TCPCHK_EVAL_WAIT;
2556 goto out;
2557}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002558
Christopher Faulet267b01b2020-04-04 10:27:09 +02002559
2560static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
2561{
2562 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2563 enum healthcheck_status status;
2564 struct buffer *msg = NULL;
2565 struct ist desc = ist(NULL);
2566 unsigned int framesz;
2567
2568
2569 memcpy(&framesz, b_head(&check->bi), 4);
2570 framesz = ntohl(framesz);
2571
2572 if (!last_read && b_data(&check->bi) < (4+framesz))
2573 goto wait_more_data;
2574
2575 memset(b_orig(&trash), 0, b_size(&trash));
2576 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
2577 status = HCHK_STATUS_L7RSP;
2578 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
2579 goto error;
2580 }
2581
Christopher Fauletec07e382020-04-07 14:56:26 +02002582 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Christopher Faulet267b01b2020-04-04 10:27:09 +02002583
2584 out:
2585 free_trash_chunk(msg);
2586 return ret;
2587
2588 error:
2589 ret = TCPCHK_EVAL_STOP;
2590 msg = alloc_trash_chunk();
2591 if (msg)
2592 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2593 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2594 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002595
2596 wait_more_data:
2597 ret = TCPCHK_EVAL_WAIT;
2598 goto out;
2599}
2600
2601static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
2602{
2603 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
2604 enum healthcheck_status status = HCHK_STATUS_CHECKED;
2605 const char *hs = NULL; /* health status */
2606 const char *as = NULL; /* admin status */
2607 const char *ps = NULL; /* performance status */
2608 const char *cs = NULL; /* maxconn */
2609 const char *err = NULL; /* first error to report */
2610 const char *wrn = NULL; /* first warning to report */
2611 char *cmd, *p;
2612
2613 /* We're getting an agent check response. The agent could
2614 * have been disabled in the mean time with a long check
2615 * still pending. It is important that we ignore the whole
2616 * response.
2617 */
2618 if (!(check->state & CHK_ST_ENABLED))
2619 goto out;
2620
2621 /* The agent supports strings made of a single line ended by the
2622 * first CR ('\r') or LF ('\n'). This line is composed of words
2623 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
2624 * line may optionally contained a description of a state change
2625 * after a sharp ('#'), which is only considered if a health state
2626 * is announced.
2627 *
2628 * Words may be composed of :
2629 * - a numeric weight suffixed by the percent character ('%').
2630 * - a health status among "up", "down", "stopped", and "fail".
2631 * - an admin status among "ready", "drain", "maint".
2632 *
2633 * These words may appear in any order. If multiple words of the
2634 * same category appear, the last one wins.
2635 */
2636
2637 p = b_head(&check->bi);
2638 while (*p && *p != '\n' && *p != '\r')
2639 p++;
2640
2641 if (!*p) {
2642 if (!last_read)
2643 goto wait_more_data;
2644
2645 /* at least inform the admin that the agent is mis-behaving */
2646 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
2647 goto out;
2648 }
2649
2650 *p = 0;
2651 cmd = b_head(&check->bi);
2652
2653 while (*cmd) {
2654 /* look for next word */
2655 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
2656 cmd++;
2657 continue;
2658 }
2659
2660 if (*cmd == '#') {
2661 /* this is the beginning of a health status description,
2662 * skip the sharp and blanks.
2663 */
2664 cmd++;
2665 while (*cmd == '\t' || *cmd == ' ')
2666 cmd++;
2667 break;
2668 }
2669
2670 /* find the end of the word so that we have a null-terminated
2671 * word between <cmd> and <p>.
2672 */
2673 p = cmd + 1;
2674 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
2675 p++;
2676 if (*p)
2677 *p++ = 0;
2678
2679 /* first, health statuses */
2680 if (strcasecmp(cmd, "up") == 0) {
2681 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
2682 status = HCHK_STATUS_L7OKD;
2683 hs = cmd;
2684 }
2685 else if (strcasecmp(cmd, "down") == 0) {
2686 check->server->check.health = 0;
2687 status = HCHK_STATUS_L7STS;
2688 hs = cmd;
2689 }
2690 else if (strcasecmp(cmd, "stopped") == 0) {
2691 check->server->check.health = 0;
2692 status = HCHK_STATUS_L7STS;
2693 hs = cmd;
2694 }
2695 else if (strcasecmp(cmd, "fail") == 0) {
2696 check->server->check.health = 0;
2697 status = HCHK_STATUS_L7STS;
2698 hs = cmd;
2699 }
2700 /* admin statuses */
2701 else if (strcasecmp(cmd, "ready") == 0) {
2702 as = cmd;
2703 }
2704 else if (strcasecmp(cmd, "drain") == 0) {
2705 as = cmd;
2706 }
2707 else if (strcasecmp(cmd, "maint") == 0) {
2708 as = cmd;
2709 }
2710 /* try to parse a weight here and keep the last one */
2711 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
2712 ps = cmd;
2713 }
2714 /* try to parse a maxconn here */
2715 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
2716 cs = cmd;
2717 }
2718 else {
2719 /* keep a copy of the first error */
2720 if (!err)
2721 err = cmd;
2722 }
2723 /* skip to next word */
2724 cmd = p;
2725 }
2726 /* here, cmd points either to \0 or to the beginning of a
2727 * description. Skip possible leading spaces.
2728 */
2729 while (*cmd == ' ' || *cmd == '\n')
2730 cmd++;
2731
2732 /* First, update the admin status so that we avoid sending other
2733 * possibly useless warnings and can also update the health if
2734 * present after going back up.
2735 */
2736 if (as) {
2737 if (strcasecmp(as, "drain") == 0)
2738 srv_adm_set_drain(check->server);
2739 else if (strcasecmp(as, "maint") == 0)
2740 srv_adm_set_maint(check->server);
2741 else
2742 srv_adm_set_ready(check->server);
2743 }
2744
2745 /* now change weights */
2746 if (ps) {
2747 const char *msg;
2748
2749 msg = server_parse_weight_change_request(check->server, ps);
2750 if (!wrn || !*wrn)
2751 wrn = msg;
2752 }
2753
2754 if (cs) {
2755 const char *msg;
2756
2757 cs += strlen("maxconn:");
2758
2759 msg = server_parse_maxconn_change_request(check->server, cs);
2760 if (!wrn || !*wrn)
2761 wrn = msg;
2762 }
2763
2764 /* and finally health status */
2765 if (hs) {
2766 /* We'll report some of the warnings and errors we have
2767 * here. Down reports are critical, we leave them untouched.
2768 * Lack of report, or report of 'UP' leaves the room for
2769 * ERR first, then WARN.
2770 */
2771 const char *msg = cmd;
2772 struct buffer *t;
2773
2774 if (!*msg || status == HCHK_STATUS_L7OKD) {
2775 if (err && *err)
2776 msg = err;
2777 else if (wrn && *wrn)
2778 msg = wrn;
2779 }
2780
2781 t = get_trash_chunk();
2782 chunk_printf(t, "via agent : %s%s%s%s",
2783 hs, *msg ? " (" : "",
2784 msg, *msg ? ")" : "");
2785 set_server_check_status(check, status, t->area);
2786 }
2787 else if (err && *err) {
2788 /* No status change but we'd like to report something odd.
2789 * Just report the current state and copy the message.
2790 */
2791 chunk_printf(&trash, "agent reports an error : %s", err);
2792 set_server_check_status(check, status/*check->status*/, trash.area);
2793 }
2794 else if (wrn && *wrn) {
2795 /* No status change but we'd like to report something odd.
2796 * Just report the current state and copy the message.
2797 */
2798 chunk_printf(&trash, "agent warns : %s", wrn);
2799 set_server_check_status(check, status/*check->status*/, trash.area);
2800 }
2801 else
2802 set_server_check_status(check, status, NULL);
2803
2804 out:
2805 return ret;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002806
2807 wait_more_data:
2808 ret = TCPCHK_EVAL_WAIT;
2809 goto out;
2810}
2811
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002812/* Evaluate a TCPCHK_ACT_CONNECT rule. It returns 1 to evaluate the next rule, 0
2813 * to wait and -1 to stop the check. */
2814static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002815{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002816 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2817 struct tcpcheck_connect *connect = &rule->connect;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01002818 struct proxy *proxy = check->proxy;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002819 struct server *s = check->server;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002820 struct task *t = check->task;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002821 struct conn_stream *cs;
2822 struct connection *conn = NULL;
2823 struct protocol *proto;
2824 struct xprt_ops *xprt;
Christopher Faulet5c288742020-03-31 08:15:58 +02002825 int status, port;
Willy Tarreauef953952014-10-02 14:30:14 +02002826
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002827 /* For a connect action we'll create a new connection. We may also have
2828 * to kill a previous one. But we don't want to leave *without* a
2829 * connection if we came here from the connection layer, hence with a
2830 * connection. Thus we'll proceed in the following order :
2831 * 1: close but not release previous connection (handled by the caller)
2832 * 2: try to get a new connection
2833 * 3: release and replace the old one on success
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002834 */
Willy Tarreau449f9522015-05-13 15:39:48 +02002835
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002836 /* 2- prepare new connection */
2837 cs = cs_new(NULL);
2838 if (!cs) {
2839 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
2840 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002841 if (rule->comment)
2842 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002843 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2844 ret = TCPCHK_EVAL_STOP;
Christopher Fauletb6102852017-11-28 10:06:29 +01002845 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002846 }
2847
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002848 /* 3- release and replace the old one on success */
2849 if (check->cs) {
2850 if (check->wait_list.events)
2851 cs->conn->xprt->unsubscribe(cs->conn, cs->conn->xprt_ctx,
2852 check->wait_list.events, &check->wait_list);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002853
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002854 /* We may have been scheduled to run, and the I/O handler
2855 * expects to have a cs, so remove the tasklet
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002856 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002857 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
2858 cs_destroy(check->cs);
2859 }
Willy Tarreaudeccd112018-06-14 18:38:55 +02002860
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002861 tasklet_set_tid(check->wait_list.tasklet, tid);
Willy Tarreauabca5b62013-12-06 14:19:25 +01002862
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002863 check->cs = cs;
2864 conn = cs->conn;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002865
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002866 /* Maybe there were an older connection we were waiting on */
2867 check->wait_list.events = 0;
2868 conn->target = s ? &s->obj_type : &proxy->obj_type;
Willy Tarreauf3d34822014-12-08 12:11:28 +01002869
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002870 /* no client address */
2871 if (!sockaddr_alloc(&conn->dst)) {
2872 status = SF_ERR_RESOURCE;
2873 goto fail_check;
2874 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002875
Christopher Faulet5c288742020-03-31 08:15:58 +02002876 /* connect to the connect rule addr if specified, otherwise the check
2877 * addr if specified on the server. otherwise, use the server addr
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002878 */
Christopher Faulet5c288742020-03-31 08:15:58 +02002879 *conn->dst = (is_addr(&connect->addr)
2880 ? connect->addr
2881 : (is_addr(&check->addr) ? check->addr : s->addr));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002882 proto = protocol_by_family(conn->dst->ss_family);
Willy Tarreau00149122017-10-04 18:05:01 +02002883
Christopher Faulet5c288742020-03-31 08:15:58 +02002884 port = 0;
2885 if (!port && connect->port)
2886 port = connect->port;
Christopher Fauletb7d30092020-03-30 15:19:03 +02002887 if (!port && connect->port_expr) {
2888 struct sample *smp;
2889
2890 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
2891 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
2892 connect->port_expr, SMP_T_SINT);
2893 if (smp)
2894 port = smp->data.u.sint;
2895 }
Christopher Faulet5c288742020-03-31 08:15:58 +02002896 if (!port && is_inet_addr(&connect->addr))
2897 port = get_host_port(&connect->addr);
2898 if (!port && check->port)
2899 port = check->port;
2900 if (!port && is_inet_addr(&check->addr))
2901 port = get_host_port(&check->addr);
2902 if (!port)
2903 port = s->svc_port;
2904 set_host_port(conn->dst, port);
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002905
Christopher Fauletbb591a12020-04-01 16:52:17 +02002906 xprt = ((connect->options & TCPCHK_OPT_SSL)
2907 ? xprt_get(XPRT_SSL)
2908 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Willy Tarreau00149122017-10-04 18:05:01 +02002909
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002910 conn_prepare(conn, proto, xprt);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002911 if (conn_install_mux(conn, &mux_pt_ops, cs, proxy, check->sess) < 0) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002912 status = SF_ERR_RESOURCE;
2913 goto fail_check;
2914 }
2915 cs_attach(cs, check, &check_conn_cb);
Willy Tarreau00149122017-10-04 18:05:01 +02002916
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002917 status = SF_ERR_INTERNAL;
2918 if (proto && proto->connect) {
2919 struct tcpcheck_rule *next;
2920 int flags = CONNECT_HAS_DATA;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02002921
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002922 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
2923 if (!next || next->action != TCPCHK_ACT_EXPECT)
2924 flags |= CONNECT_DELACK_ALWAYS;
2925 status = proto->connect(conn, flags);
2926 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002927
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002928#ifdef USE_OPENSSL
Christopher Fauletbb591a12020-04-01 16:52:17 +02002929 if (status == SF_ERR_NONE) {
2930 if (connect->sni)
2931 ssl_sock_set_servername(conn, connect->sni);
2932 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
2933 ssl_sock_set_servername(conn, s->check.sni);
2934
2935 if (connect->alpn)
2936 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
2937 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
2938 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002939 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02002940#endif
Christopher Fauletbb591a12020-04-01 16:52:17 +02002941 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
2942 conn->send_proxy_ofs = 1;
2943 conn->flags |= CO_FL_SOCKS4;
2944 }
2945 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
2946 conn->send_proxy_ofs = 1;
2947 conn->flags |= CO_FL_SOCKS4;
2948 }
2949
2950 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
2951 conn->send_proxy_ofs = 1;
2952 conn->flags |= CO_FL_SEND_PROXY;
2953 }
2954 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
2955 conn->send_proxy_ofs = 1;
2956 conn->flags |= CO_FL_SEND_PROXY;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002957 }
Willy Tarreauca79f592019-07-17 19:04:47 +02002958
Christopher Fauletbb591a12020-04-01 16:52:17 +02002959 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
2960 /* Some servers don't like reset on close */
2961 fdtab[cs->conn->handle.fd].linger_risk = 0;
2962 }
2963
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002964 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
2965 if (xprt_add_hs(conn) < 0)
2966 status = SF_ERR_RESOURCE;
2967 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002968
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002969 fail_check:
2970 /* It can return one of :
2971 * - SF_ERR_NONE if everything's OK
2972 * - SF_ERR_SRVTO if there are no more servers
2973 * - SF_ERR_SRVCL if the connection was refused by the server
2974 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
2975 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2976 * - SF_ERR_INTERNAL for any other purely internal errors
2977 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2978 * Note that we try to prevent the network stack from sending the ACK during the
2979 * connect() when a pure TCP check is used (without PROXY protocol).
2980 */
2981 switch (status) {
2982 case SF_ERR_NONE:
2983 /* we allow up to min(inter, timeout.connect) for a connection
2984 * to establish but only when timeout.check is set as it may be
2985 * to short for a full check otherwise
2986 */
2987 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002988
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002989 if (proxy->timeout.check && proxy->timeout.connect) {
2990 int t_con = tick_add(now_ms, proxy->timeout.connect);
2991 t->expire = tick_first(t->expire, t_con);
2992 }
2993 break;
2994 case SF_ERR_SRVTO: /* ETIMEDOUT */
2995 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
2996 chunk_printf(&trash, "TCPCHK error establishing connection at step %d: %s",
2997 tcpcheck_get_step_id(check, rule), strerror(errno));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002998 if (rule->comment)
2999 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003000 set_server_check_status(check, HCHK_STATUS_L4CON, trash.area);
3001 ret = TCPCHK_EVAL_STOP;
3002 goto out;
3003 case SF_ERR_PRXCOND:
3004 case SF_ERR_RESOURCE:
3005 case SF_ERR_INTERNAL:
3006 chunk_printf(&trash, "TCPCHK error establishing connection at step %d",
3007 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003008 if (rule->comment)
3009 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003010 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3011 ret = TCPCHK_EVAL_STOP;
3012 goto out;
3013 }
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003014
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003015 /* don't do anything until the connection is established */
3016 if (conn->flags & CO_FL_WAIT_XPRT) {
3017 ret = TCPCHK_EVAL_WAIT;
3018 goto out;
3019 }
Willy Tarreaube373152018-09-06 11:45:30 +02003020
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003021 out:
3022 if (conn && check->result == CHK_RES_FAILED)
3023 conn->flags |= CO_FL_ERROR;
3024 return ret;
3025}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02003026
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003027/* Evaluate a TCPCHK_ACT_SEND rule. It returns 1 to evaluate the next rule, 0
3028 * to wait and -1 to stop the check. */
3029static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
3030{
3031 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3032 struct tcpcheck_send *send = &rule->send;
3033 struct conn_stream *cs = check->cs;
3034 struct connection *conn = cs_conn(cs);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003035 struct buffer *tmp = NULL;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003036
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003037 /* reset the read & write buffer */
3038 b_reset(&check->bi);
3039 b_reset(&check->bo);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01003040
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003041 switch (send->type) {
3042 case TCPCHK_SEND_STRING:
3043 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003044 if (istlen(send->data) >= b_size(&check->bo)) {
3045 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
3046 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
3047 tcpcheck_get_step_id(check, rule));
3048 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3049 ret = TCPCHK_EVAL_STOP;
3050 goto out;
3051 }
3052 b_putist(&check->bo, send->data);
3053 break;
3054 case TCPCHK_SEND_STRING_LF:
3055 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
3056 if (!b_data(&check->bo))
3057 goto out;
3058 break;
3059 case TCPCHK_SEND_BINARY_LF:
3060 tmp = alloc_trash_chunk();
3061 if (!tmp)
3062 goto error_lf;
3063 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
3064 if (!b_data(tmp))
3065 goto out;
3066 tmp->area[tmp->data] = '\0';
3067 b_set_data(&check->bo, b_size(&check->bo));
3068 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
3069 goto error_lf;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003070 break;
3071 case TCPCHK_SEND_UNDEF:
3072 /* Should never happen. */
3073 ret = TCPCHK_EVAL_STOP;
3074 goto out;
3075 };
Christopher Fauleta202d1d2020-03-26 17:38:49 +01003076
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003077 if (conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0) <= 0) {
3078 ret = TCPCHK_EVAL_WAIT;
3079 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))
3080 ret = TCPCHK_EVAL_STOP;
3081 goto out;
3082 }
3083 if (b_data(&check->bo)) {
3084 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3085 ret = TCPCHK_EVAL_WAIT;
3086 goto out;
3087 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003088
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003089 out:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003090 free_trash_chunk(tmp);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003091 return ret;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003092
3093 error_lf:
3094 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
3095 tcpcheck_get_step_id(check, rule));
3096 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3097 ret = TCPCHK_EVAL_STOP;
3098 goto out;
3099
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003100}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01003101
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003102/* Evaluate a TCPCHK_ACT_EXPECT rule. It returns 1 to evaluate the next rule, 0
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003103 * to wait and -1 to stop the check.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003104 */
3105static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
3106{
3107 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Fauletec07e382020-04-07 14:56:26 +02003108 struct tcpcheck_expect *expect = &rule->expect;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003109 struct buffer *msg = NULL;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003110 int match;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003111
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003112 /* The current expect might need more data than the previous one, check again
3113 * that the minimum amount data required to match is respected.
3114 */
3115 if (!last_read) {
3116 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003117 (b_data(&check->bi) < istlen(expect->data))) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003118 ret = TCPCHK_EVAL_WAIT;
3119 goto out;
3120 }
3121 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
3122 ret = TCPCHK_EVAL_WAIT;
3123 goto out;
3124 }
3125 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003126
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003127 /* Make GCC happy ; initialize match to a failure state. */
3128 match = expect->inverse;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003129
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003130 switch (expect->type) {
3131 case TCPCHK_EXPECT_STRING:
3132 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003133 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 +02003134 break;
3135 case TCPCHK_EXPECT_REGEX:
3136 if (expect->with_capture)
3137 match = regex_exec_match2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1),
3138 MAX_MATCH, pmatch, 0);
3139 else
3140 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
3141 break;
Willy Tarreau7df8ca62019-07-15 10:57:51 +02003142
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003143 case TCPCHK_EXPECT_REGEX_BINARY:
3144 chunk_reset(&trash);
3145 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
3146 if (expect->with_capture)
3147 match = regex_exec_match2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1),
3148 MAX_MATCH, pmatch, 0);
3149 else
3150 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
3151 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003152 case TCPCHK_EXPECT_CUSTOM:
3153 if (expect->custom)
3154 ret = expect->custom(check, rule, last_read);
3155 goto out;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003156 case TCPCHK_EXPECT_UNDEF:
3157 /* Should never happen. */
3158 ret = TCPCHK_EVAL_STOP;
3159 goto out;
3160 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003161
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003162
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003163 /* Wait for more data on mismatch only if no minimum is defined (-1),
3164 * otherwise the absence of match is already conclusive.
3165 */
3166 if (!match && !last_read && (expect->min_recv == -1)) {
3167 ret = TCPCHK_EVAL_WAIT;
3168 goto out;
3169 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003170
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003171 /* Result as expected, next rule. */
3172 if (match ^ expect->inverse)
3173 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003174
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01003175
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003176 /* From this point on, we matched something we did not want, this is an error state. */
3177 ret = TCPCHK_EVAL_STOP;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003178 msg = alloc_trash_chunk();
Christopher Faulet206368d2020-04-03 14:51:06 +02003179 if (msg)
3180 tcpcheck_onerror_message(msg, check, rule, match, ist(NULL));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003181 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
Christopher Faulet206368d2020-04-03 14:51:06 +02003182 free_trash_chunk(msg);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003183 ret = TCPCHK_EVAL_STOP;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003184
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003185 out:
3186 return ret;
3187}
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003188
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003189/* Evaluate a TCPCHK_ACT_ACTION_KW rule. It returns 1 to evaluate the next rule, 0
3190 * to wait and -1 to stop the check.
3191 */
3192static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
3193{
3194 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3195 struct act_rule *act_rule;
3196 enum act_return act_ret;
3197
3198 act_rule =rule->action_kw.rule;
3199 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
3200 if (act_ret != ACT_RET_CONT) {
3201 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
3202 tcpcheck_get_step_id(check, rule));
3203 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3204 ret = TCPCHK_EVAL_STOP;
3205 }
3206
3207 return ret;
3208}
3209
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003210/* proceed with next steps for the TCP checks <check>. Note that this is called
3211 * both from the connection's wake() callback and from the check scheduling task.
3212 * It returns 0 on normal cases, or <0 if a close() has happened on an existing
3213 * connection, presenting the risk of an fd replacement.
3214 *
3215 * Please do NOT place any return statement in this function and only leave
3216 * via the out_end_tcpcheck label after setting retcode.
3217 */
3218static int tcpcheck_main(struct check *check)
3219{
3220 struct tcpcheck_rule *rule;
3221 struct conn_stream *cs = check->cs;
3222 struct connection *conn = cs_conn(cs);
3223 int must_read = 1, last_read = 0;
3224 int ret, retcode = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003225
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003226 /* here, we know that the check is complete or that it failed */
3227 if (check->result != CHK_RES_UNKNOWN)
3228 goto out_end_tcpcheck;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003229
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003230 /* 1- check for connection error, if any */
3231 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3232 goto out_end_tcpcheck;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003233
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003234 /* 2- check if we are waiting for the connection establishment. It only
3235 * happens during TCPCHK_ACT_CONNECT. */
3236 if (conn && (conn->flags & CO_FL_WAIT_XPRT))
3237 goto out;
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003238
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003239 /* 3- check for pending outgoing data. It only happens during TCPCHK_ACT_SEND. */
3240 if (conn && b_data(&check->bo)) {
3241 ret = conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
3242 if (ret <= 0) {
3243 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3244 goto out_end_tcpcheck;
3245 goto out;
3246 }
3247 if (b_data(&check->bo)) {
3248 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3249 goto out;
3250 }
3251 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003252
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003253 /* Now evaluate the tcp-check rules */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003254
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003255 /* If check->current_step is defined, we are in resume condition. For
3256 * TCPCHK_ACT_CONNECT and TCPCHK_ACT_SEND rules, we must go to the next
3257 * rule before resuming the evaluation. For TCPCHK_ACT_EXPECT, we
3258 * re-evaluate the current rule. Others cannot yield.
3259 */
3260 if (check->current_step) {
3261 if (check->current_step->action == TCPCHK_ACT_CONNECT ||
3262 check->current_step->action == TCPCHK_ACT_SEND)
3263 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
3264 else
3265 rule = check->current_step;
3266 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003267 else {
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003268 struct tcpcheck_var *var;
3269
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003270 /* First evaluation, create a session */
Gaetan Rivet13a50432020-02-21 18:13:44 +01003271 check->sess = session_new(&checks_fe, NULL, (check->server ? &check->server->obj_type : NULL));
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003272 if (!check->sess) {
3273 chunk_printf(&trash, "TCPCHK error allocating check session");
3274 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3275 goto out_end_tcpcheck;
3276 }
Gaetan Rivet13a50432020-02-21 18:13:44 +01003277 vars_init(&check->vars, SCOPE_CHECK);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003278 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003279
3280 /* Preset tcp-check variables */
3281 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
3282 struct sample smp;
3283
3284 memset(&smp, 0, sizeof(smp));
3285 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
3286 smp.data = var->data;
3287 vars_set_by_name_ifexist(var->name.ptr, var->name.len, &smp);
3288 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003289 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003290
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003291 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003292 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003293
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003294 switch (rule->action) {
3295 case TCPCHK_ACT_CONNECT:
3296 check->current_step = rule;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003297
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003298 /* close but not release yet previous connection */
3299 if (check->cs) {
3300 cs_close(check->cs);
3301 retcode = -1; /* do not reuse the fd in the caller! */
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003302 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003303 eval_ret = tcpcheck_eval_connect(check, rule);
3304 must_read = 1; last_read = 0;
3305 break;
3306 case TCPCHK_ACT_SEND:
3307 check->current_step = rule;
3308 eval_ret = tcpcheck_eval_send(check, rule);
3309 must_read = 1;
3310 break;
3311 case TCPCHK_ACT_EXPECT:
3312 check->current_step = rule;
3313 if (must_read) {
3314 if (check->proxy->timeout.check)
3315 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003316
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003317 /* If we already subscribed, then we tried to received and
3318 * failed, so there's no point trying again.
3319 */
3320 if (check->wait_list.events & SUB_RETRY_RECV)
3321 goto out;
3322 if (conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0) <= 0) {
3323 if (conn->flags & (CO_FL_ERROR|CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
3324 last_read = 1;
3325 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
3326 /* Report network errors only if we got no other data. Otherwise
3327 * we'll let the upper layers decide whether the response is OK
3328 * or not. It is very common that an RST sent by the server is
3329 * reported as an error just after the last data chunk.
3330 */
3331 goto out_end_tcpcheck;
3332 }
3333 }
3334 else {
3335 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
3336 goto out;
3337 }
3338 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003339
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003340 /* buffer full, don't wait for more data */
3341 if (b_full(&check->bi))
3342 last_read = 1;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003343
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003344 /* Check that response body is not empty... */
3345 if (!b_data(&check->bi)) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003346 if (!last_read)
3347 goto out;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003348
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003349 /* empty response */
3350 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
3351 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003352 if (rule->comment)
3353 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003354 set_server_check_status(check, rule->expect.err_status, trash.area);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003355 ret = -1;
3356 goto out_end_tcpcheck;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003357 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003358 must_read = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003359 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003360
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003361 eval_ret = tcpcheck_eval_expect(check, rule, last_read);
3362 if (eval_ret == TCPCHK_EVAL_WAIT) {
3363 check->current_step = rule->expect.head;
3364 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003365 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003366 break;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003367 case TCPCHK_ACT_ACTION_KW:
3368 /* Don't update the current step */
3369 eval_ret = tcpcheck_eval_action_kw(check, rule);
3370 break;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003371 default:
3372 /* Otherwise, just go to the next one and don't update
3373 * the current step
3374 */
3375 eval_ret = TCPCHK_EVAL_CONTINUE;
3376 break;
3377 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003378
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003379 switch (eval_ret) {
3380 case TCPCHK_EVAL_CONTINUE:
3381 break;
3382 case TCPCHK_EVAL_WAIT:
3383 goto out;
3384 case TCPCHK_EVAL_STOP:
3385 goto out_end_tcpcheck;
Baptiste Assmann248f1172018-03-01 21:49:01 +01003386 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003387 }
3388
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003389 /* All rules was evaluated */
Christopher Fauletdf38f882020-04-07 16:04:38 +02003390 if (check->current_step) {
3391 rule = check->current_step;
3392
3393 if (rule->action == TCPCHK_ACT_EXPECT) {
3394 struct buffer *msg = alloc_trash_chunk();
3395
3396 if (msg)
3397 tcpcheck_onsuccess_message(msg, check, rule, ist(NULL));
3398 set_server_check_status(check, rule->expect.ok_status,
3399 (msg ? b_head(msg) : "(tcp-check)"));
3400 free_trash_chunk(msg);
3401 }
3402 else if (rule->action == TCPCHK_ACT_CONNECT) {
3403 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
3404 enum healthcheck_status status = ((conn && ssl_sock_is_ssl(conn)) ? HCHK_STATUS_L6OK : HCHK_STATUS_L4OK);
3405
3406 set_server_check_status(check, status, msg);
3407 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003408 }
Christopher Fauletec07e382020-04-07 14:56:26 +02003409 else
3410 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003411
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003412 out_end_tcpcheck:
Willy Tarreauef91c932019-07-23 14:37:47 +02003413 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003414 chk_report_conn_err(check, errno, 0);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003415
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003416 /* cleanup before leaving */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003417 check->current_step = NULL;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003418 if (check->sess != NULL) {
Gaetan Rivet13a50432020-02-21 18:13:44 +01003419 vars_prune(&check->vars, check->sess, NULL);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003420 session_free(check->sess);
3421 check->sess = NULL;
3422 }
3423 out:
3424 return retcode;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003425}
3426
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003427static const char *init_check(struct check *check, int type)
Simon Hormanb1900d52015-01-30 11:22:54 +09003428{
3429 check->type = type;
3430
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003431 b_reset(&check->bi); check->bi.size = global.tune.chksize;
3432 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Simon Hormanb1900d52015-01-30 11:22:54 +09003433
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003434 check->bi.area = calloc(check->bi.size, sizeof(char));
3435 check->bo.area = calloc(check->bo.size, sizeof(char));
3436
3437 if (!check->bi.area || !check->bo.area)
Simon Hormanb1900d52015-01-30 11:22:54 +09003438 return "out of memory while allocating check buffer";
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003439
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003440 check->wait_list.tasklet = tasklet_new();
3441 if (!check->wait_list.tasklet)
Ilya Shipitsind4259502020-04-08 01:07:56 +05003442 return "out of memory while allocating check tasklet";
Willy Tarreau4f6516d2018-12-19 13:59:17 +01003443 check->wait_list.events = 0;
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003444 check->wait_list.tasklet->process = event_srv_chk_io;
3445 check->wait_list.tasklet->context = check;
Simon Hormanb1900d52015-01-30 11:22:54 +09003446 return NULL;
3447}
3448
Simon Hormanbfb5d332015-01-30 11:22:55 +09003449void free_check(struct check *check)
3450{
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003451 task_destroy(check->task);
3452 if (check->wait_list.tasklet)
3453 tasklet_free(check->wait_list.tasklet);
3454
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003455 free(check->bi.area);
3456 free(check->bo.area);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003457 if (check->cs) {
3458 free(check->cs->conn);
3459 check->cs->conn = NULL;
3460 cs_free(check->cs);
3461 check->cs = NULL;
3462 }
Simon Hormanbfb5d332015-01-30 11:22:55 +09003463}
3464
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003465static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
3466{
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003467 struct logformat_node *lf, *lfb;
3468
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003469 if (!rule)
3470 return;
3471
3472 free(rule->comment);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003473 switch (rule->action) {
3474 case TCPCHK_ACT_SEND:
3475 switch (rule->send.type) {
3476 case TCPCHK_SEND_STRING:
3477 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003478 free(rule->send.data.ptr);
3479 break;
3480 case TCPCHK_SEND_STRING_LF:
3481 case TCPCHK_SEND_BINARY_LF:
3482 list_for_each_entry_safe(lf, lfb, &rule->send.fmt, list) {
3483 LIST_DEL(&lf->list);
3484 release_sample_expr(lf->expr);
3485 free(lf->arg);
3486 free(lf);
3487 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003488 break;
3489 case TCPCHK_SEND_UNDEF:
3490 break;
3491 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003492 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003493 case TCPCHK_ACT_EXPECT:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003494 list_for_each_entry_safe(lf, lfb, &rule->expect.onerror_fmt, list) {
3495 LIST_DEL(&lf->list);
3496 release_sample_expr(lf->expr);
3497 free(lf->arg);
3498 free(lf);
3499 }
3500 list_for_each_entry_safe(lf, lfb, &rule->expect.onsuccess_fmt, list) {
3501 LIST_DEL(&lf->list);
3502 release_sample_expr(lf->expr);
3503 free(lf->arg);
3504 free(lf);
3505 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003506 release_sample_expr(rule->expect.status_expr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003507 switch (rule->expect.type) {
3508 case TCPCHK_EXPECT_STRING:
3509 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003510 free(rule->expect.data.ptr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003511 break;
3512 case TCPCHK_EXPECT_REGEX:
3513 case TCPCHK_EXPECT_REGEX_BINARY:
3514 regex_free(rule->expect.regex);
3515 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003516 case TCPCHK_EXPECT_CUSTOM:
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003517 case TCPCHK_EXPECT_UNDEF:
3518 break;
3519 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003520 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003521 case TCPCHK_ACT_CONNECT:
Christopher Faulet79b31d42020-03-30 13:00:05 +02003522 free(rule->connect.sni);
Christopher Faulet98572322020-03-30 13:16:44 +02003523 free(rule->connect.alpn);
Christopher Fauletb7d30092020-03-30 15:19:03 +02003524 release_sample_expr(rule->connect.port_expr);
Christopher Faulet79b31d42020-03-30 13:00:05 +02003525 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003526 case TCPCHK_ACT_COMMENT:
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003527 break;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01003528 case TCPCHK_ACT_ACTION_KW:
3529 free(rule->action_kw.rule);
3530 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003531 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003532
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003533 if (in_pool)
3534 pool_free(pool_head_tcpcheck_rule, rule);
3535 else
3536 free(rule);
3537}
3538
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003539
Christopher Fauletce355072020-04-02 11:44:39 +02003540static struct tcpcheck_var *tcpcheck_var_create(const char *name)
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003541{
3542 struct tcpcheck_var *var = NULL;
3543
3544 var = calloc(1, sizeof(*var));
3545 if (var == NULL)
3546 return NULL;
3547
3548 var->name = ist2(strdup(name), strlen(name));
3549 if (var->name.ptr == NULL) {
3550 free(var);
3551 return NULL;
3552 }
3553
3554 LIST_INIT(&var->list);
3555 return var;
3556}
3557
3558static void tcpcheck_var_release(struct tcpcheck_var *var)
3559{
3560 if (!var)
3561 return;
3562
3563 free(var->name.ptr);
3564 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
3565 free(var->data.u.str.area);
3566 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
3567 free(var->data.u.meth.str.area);
3568 free(var);
3569}
3570
3571int dup_tcpcheck_vars(struct list *dst, struct list *src)
3572{
3573 struct tcpcheck_var *var, *new = NULL;
3574
3575 list_for_each_entry(var, src, list) {
3576 new = tcpcheck_var_create(var->name.ptr);
3577 if (!new)
3578 goto error;
3579 new->data.type = var->data.type;
3580 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
3581 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3582 goto error;
3583 if (var->data.type == SMP_T_STR)
3584 new->data.u.str.area[new->data.u.str.data] = 0;
3585 }
3586 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
3587 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3588 goto error;
3589 new->data.u.str.area[new->data.u.str.data] = 0;
3590 new->data.u.meth.meth = var->data.u.meth.meth;
3591 }
3592 else
3593 new->data.u = var->data.u;
3594 LIST_ADDQ(dst, &new->list);
3595 }
3596 return 1;
3597
3598 error:
3599 free(new);
3600 return 0;
3601}
3602
3603static void free_tcpcheck_vars(struct list *vars)
3604{
3605 struct tcpcheck_var *var, *back;
3606
3607 list_for_each_entry_safe(var, back, vars, list) {
3608 LIST_DEL(&var->list);
3609 tcpcheck_var_release(var);
3610 }
3611}
3612
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003613void email_alert_free(struct email_alert *alert)
3614{
3615 struct tcpcheck_rule *rule, *back;
3616
3617 if (!alert)
3618 return;
3619
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003620 if (alert->rules.list) {
3621 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
3622 LIST_DEL(&rule->list);
3623 free_tcpcheck(rule, 1);
3624 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003625 free_tcpcheck_vars(&alert->rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003626 free(alert->rules.list);
3627 alert->rules.list = NULL;
Christopher Fauletde1a75b2017-10-23 15:38:19 +02003628 }
Willy Tarreaubafbe012017-11-24 17:34:44 +01003629 pool_free(pool_head_email_alert, alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003630}
3631
Olivier Houchard9f6af332018-05-25 14:04:04 +02003632static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003633{
Olivier Houchard9f6af332018-05-25 14:04:04 +02003634 struct check *check = context;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003635 struct email_alertq *q;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003636 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003637
3638 q = container_of(check, typeof(*q), check);
3639
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003640 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003641 while (1) {
3642 if (!(check->state & CHK_ST_ENABLED)) {
3643 if (LIST_ISEMPTY(&q->email_alerts)) {
3644 /* All alerts processed, queue the task */
3645 t->expire = TICK_ETERNITY;
3646 task_queue(t);
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003647 goto end;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003648 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003649
3650 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003651 LIST_DEL(&alert->list);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003652 t->expire = now_ms;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003653 check->tcpcheck_rules = &alert->rules;
Olivier Houchard0923fa42019-01-11 18:43:04 +01003654 check->status = HCHK_STATUS_INI;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003655 check->state |= CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003656 }
3657
Olivier Houchard9f6af332018-05-25 14:04:04 +02003658 process_chk(t, context, state);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003659 if (check->state & CHK_ST_INPROGRESS)
3660 break;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003661
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003662 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003663 email_alert_free(alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003664 check->tcpcheck_rules = NULL;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003665 check->server = NULL;
3666 check->state &= ~CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003667 }
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003668 end:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003669 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003670 return t;
3671}
3672
Christopher Faulet0108bb32017-10-20 21:34:32 +02003673/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
3674 *
3675 * The function returns 1 in success case, otherwise, it returns 0 and err is
3676 * filled.
3677 */
3678int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003679{
Christopher Faulet0108bb32017-10-20 21:34:32 +02003680 struct mailer *mailer;
3681 struct email_alertq *queues;
3682 const char *err_str;
3683 int i = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003684
Christopher Faulet0108bb32017-10-20 21:34:32 +02003685 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
3686 memprintf(err, "out of memory while allocating mailer alerts queues");
mildis5ab01cb2018-10-02 16:46:34 +02003687 goto fail_no_queue;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003688 }
3689
Christopher Faulet0108bb32017-10-20 21:34:32 +02003690 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
3691 struct email_alertq *q = &queues[i];
3692 struct check *check = &q->check;
3693 struct task *t;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003694
3695 LIST_INIT(&q->email_alerts);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003696 HA_SPIN_INIT(&q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003697 check->inter = mls->timeout.mail;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003698 check->rise = DEF_AGENT_RISETIME;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01003699 check->proxy = p;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003700 check->fall = DEF_AGENT_FALLTIME;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003701 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
3702 memprintf(err, "%s", err_str);
3703 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003704 }
3705
3706 check->xprt = mailer->xprt;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003707 check->addr = mailer->addr;
Christopher Fauletb797ae12018-03-27 15:35:35 +02003708 check->port = get_host_port(&mailer->addr);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003709
Emeric Brunc60def82017-09-27 14:59:38 +02003710 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003711 memprintf(err, "out of memory while allocating mailer alerts task");
3712 goto error;
3713 }
3714
3715 check->task = t;
3716 t->process = process_email_alert;
3717 t->context = check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003718
Christopher Faulet0108bb32017-10-20 21:34:32 +02003719 /* check this in one ms */
3720 t->expire = TICK_ETERNITY;
3721 check->start = now;
3722 task_queue(t);
3723 }
3724
3725 mls->users++;
3726 free(p->email_alert.mailers.name);
3727 p->email_alert.mailers.m = mls;
3728 p->email_alert.queues = queues;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003729 return 0;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003730
3731 error:
3732 for (i = 0; i < mls->count; i++) {
3733 struct email_alertq *q = &queues[i];
3734 struct check *check = &q->check;
3735
Christopher Faulet0108bb32017-10-20 21:34:32 +02003736 free_check(check);
3737 }
3738 free(queues);
mildis5ab01cb2018-10-02 16:46:34 +02003739 fail_no_queue:
Christopher Faulet0108bb32017-10-20 21:34:32 +02003740 return 1;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003741}
3742
3743
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003744static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003745{
Gaetan Rivet4038b942020-02-26 16:19:40 +01003746 struct tcpcheck_rule *tcpcheck, *prev_check;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003747 struct tcpcheck_expect *expect;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003748
Willy Tarreaubafbe012017-11-24 17:34:44 +01003749 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003750 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003751 memset(tcpcheck, 0, sizeof(*tcpcheck));
Gaetan Rivetb616add2020-02-07 15:37:17 +01003752 tcpcheck->action = TCPCHK_ACT_EXPECT;
3753
3754 expect = &tcpcheck->expect;
3755 expect->type = TCPCHK_EXPECT_STRING;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003756 LIST_INIT(&expect->onerror_fmt);
3757 LIST_INIT(&expect->onsuccess_fmt);
Christopher Fauletec07e382020-04-07 14:56:26 +02003758 expect->ok_status = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003759 expect->err_status = HCHK_STATUS_L7RSP;
3760 expect->tout_status = HCHK_STATUS_L7TOUT;
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003761 expect->data = ist2(strdup(str), strlen(str));
3762 if (!expect->data.ptr) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003763 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003764 return 0;
3765 }
3766
Gaetan Rivet4038b942020-02-26 16:19:40 +01003767 /* All tcp-check expect points back to the first inverse expect rule
3768 * in a chain of one or more expect rule, potentially itself.
3769 */
Gaetan Rivetb616add2020-02-07 15:37:17 +01003770 tcpcheck->expect.head = tcpcheck;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003771 list_for_each_entry_rev(prev_check, rules->list, list) {
Gaetan Rivet4038b942020-02-26 16:19:40 +01003772 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Gaetan Rivetb616add2020-02-07 15:37:17 +01003773 if (prev_check->expect.inverse)
3774 tcpcheck->expect.head = prev_check;
Gaetan Rivet4038b942020-02-26 16:19:40 +01003775 continue;
3776 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003777 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet4038b942020-02-26 16:19:40 +01003778 break;
3779 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003780 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003781 return 1;
3782}
3783
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003784static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003785{
3786 struct tcpcheck_rule *tcpcheck;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003787 struct tcpcheck_send *send;
Willy Tarreau64345aa2016-08-10 19:29:09 +02003788 const char *in;
3789 char *dst;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003790 int i;
3791
Willy Tarreaubafbe012017-11-24 17:34:44 +01003792 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003793 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003794 memset(tcpcheck, 0, sizeof(*tcpcheck));
3795 tcpcheck->action = TCPCHK_ACT_SEND;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003796
3797 send = &tcpcheck->send;
3798 send->type = TCPCHK_SEND_STRING;
3799
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003800 for (i = 0; strs[i]; i++)
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003801 send->data.len += strlen(strs[i]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003802
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003803 send->data.ptr = malloc(send->data.len + 1);
3804 if (!isttest(send->data)) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003805 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003806 return 0;
3807 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003808
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003809 dst = send->data.ptr;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003810 for (i = 0; strs[i]; i++)
Willy Tarreau64345aa2016-08-10 19:29:09 +02003811 for (in = strs[i]; (*dst = *in++); dst++);
3812 *dst = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003813
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003814 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003815 return 1;
3816}
3817
Christopher Faulet0108bb32017-10-20 21:34:32 +02003818static int enqueue_one_email_alert(struct proxy *p, struct server *s,
3819 struct email_alertq *q, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003820{
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003821 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003822 struct tcpcheck_rule *tcpcheck;
3823 struct check *check = &q->check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003824
Willy Tarreaubafbe012017-11-24 17:34:44 +01003825 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003826 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003827 LIST_INIT(&alert->list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003828 alert->rules.flags = 0;
3829 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
3830 if (!alert->rules.list)
3831 goto error;
3832 LIST_INIT(alert->rules.list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003833 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
Christopher Faulet0108bb32017-10-20 21:34:32 +02003834 alert->srv = s;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003835
Willy Tarreaubafbe012017-11-24 17:34:44 +01003836 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003837 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003838 memset(tcpcheck, 0, sizeof(*tcpcheck));
3839 tcpcheck->action = TCPCHK_ACT_CONNECT;
3840 tcpcheck->comment = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003841
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003842 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003843
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003844 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003845 goto error;
3846
3847 {
3848 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003849 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003850 goto error;
3851 }
3852
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003853 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003854 goto error;
3855
3856 {
3857 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003858 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003859 goto error;
3860 }
3861
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003862 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003863 goto error;
3864
3865 {
3866 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003867 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003868 goto error;
3869 }
3870
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003871 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003872 goto error;
3873
3874 {
3875 const char * const strs[2] = { "DATA\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003876 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003877 goto error;
3878 }
3879
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003880 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003881 goto error;
3882
3883 {
3884 struct tm tm;
3885 char datestr[48];
3886 const char * const strs[18] = {
Pieter Baauw5e0964e2016-02-13 16:27:35 +01003887 "From: ", p->email_alert.from, "\r\n",
3888 "To: ", p->email_alert.to, "\r\n",
3889 "Date: ", datestr, "\r\n",
3890 "Subject: [HAproxy Alert] ", msg, "\r\n",
3891 "\r\n",
3892 msg, "\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003893 "\r\n",
Pieter Baauwed35c372015-07-22 19:51:54 +02003894 ".\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003895 NULL
3896 };
3897
3898 get_localtime(date.tv_sec, &tm);
3899
3900 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
3901 goto error;
3902 }
3903
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003904 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003905 goto error;
3906 }
3907
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003908 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003909 goto error;
3910
3911 {
3912 const char * const strs[2] = { "QUIT\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003913 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003914 goto error;
3915 }
3916
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003917 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003918 goto error;
3919
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003920 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003921 task_wakeup(check->task, TASK_WOKEN_MSG);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003922 LIST_ADDQ(&q->email_alerts, &alert->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003923 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003924 return 1;
3925
3926error:
3927 email_alert_free(alert);
3928 return 0;
3929}
3930
Christopher Faulet0108bb32017-10-20 21:34:32 +02003931static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003932{
3933 int i;
3934 struct mailer *mailer;
3935
3936 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
3937 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003938 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003939 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003940 return;
3941 }
3942 }
3943
3944 return;
3945}
3946
3947/*
3948 * Send email alert if configured.
3949 */
Simon Horman64e34162015-02-06 11:11:57 +09003950void send_email_alert(struct server *s, int level, const char *format, ...)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003951{
3952 va_list argp;
3953 char buf[1024];
3954 int len;
3955 struct proxy *p = s->proxy;
3956
Christopher Faulet0108bb32017-10-20 21:34:32 +02003957 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003958 return;
3959
3960 va_start(argp, format);
3961 len = vsnprintf(buf, sizeof(buf), format, argp);
3962 va_end(argp);
3963
Thierry FOURNIER62c8a212017-02-09 12:19:27 +01003964 if (len < 0 || len >= sizeof(buf)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003965 ha_alert("Email alert [%s] could not format message\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003966 return;
3967 }
3968
Christopher Faulet0108bb32017-10-20 21:34:32 +02003969 enqueue_email_alert(p, s, buf);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003970}
3971
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003972/*
3973 * Return value:
3974 * the port to be used for the health check
3975 * 0 in case no port could be found for the check
3976 */
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003977static int srv_check_healthcheck_port(struct check *chk)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003978{
3979 int i = 0;
3980 struct server *srv = NULL;
3981
3982 srv = chk->server;
3983
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003984 /* by default, we use the health check port ocnfigured */
3985 if (chk->port > 0)
3986 return chk->port;
3987
3988 /* try to get the port from check_core.addr if check.port not set */
3989 i = get_host_port(&chk->addr);
3990 if (i > 0)
3991 return i;
3992
3993 /* try to get the port from server address */
3994 /* prevent MAPPORTS from working at this point, since checks could
3995 * not be performed in such case (MAPPORTS impose a relative ports
3996 * based on live traffic)
3997 */
3998 if (srv->flags & SRV_F_MAPPORTS)
3999 return 0;
Willy Tarreau04276f32017-01-06 17:41:29 +01004000
4001 i = srv->svc_port; /* by default */
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004002 if (i > 0)
4003 return i;
4004
4005 return 0;
4006}
4007
Willy Tarreau172f5ce2018-11-26 11:21:50 +01004008REGISTER_POST_CHECK(start_checks);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02004009
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004010static int check_proxy_tcpcheck(struct proxy *px)
4011{
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004012 struct tcpcheck_rule *chk, *back;
4013 char *comment = NULL;
4014 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004015 int ret = 0;
4016
Christopher Faulet404f9192020-04-09 23:13:54 +02004017 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004018 goto out;
4019
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004020 if (!px->tcpcheck_rules.list) {
Christopher Faulet404f9192020-04-09 23:13:54 +02004021 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4022 ret |= ERR_ALERT | ERR_FATAL;
4023 goto out;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004024 }
4025
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004026 /* If there is no connect rule preceeding all send / expect rules, an
4027 * implicit one is inserted before all others
4028 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004029 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004030 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4031 chk = calloc(1, sizeof(*chk));
4032 if (!chk) {
4033 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4034 "(out of memory).\n", px->id);
4035 ret |= ERR_ALERT | ERR_FATAL;
4036 goto out;
4037 }
4038 chk->action = TCPCHK_ACT_CONNECT;
Christopher Fauletbb591a12020-04-01 16:52:17 +02004039 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004040 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004041 }
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004042
4043 /* Now remove comment rules */
4044 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4045 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4046 free(comment);
4047 comment = NULL;
4048 }
4049
4050 prev_action = chk->action;
4051 switch (chk->action) {
4052 case TCPCHK_ACT_COMMENT:
4053 free(comment);
4054 comment = chk->comment;
4055 LIST_DEL(&chk->list);
4056 free(chk);
4057 break;
4058 case TCPCHK_ACT_CONNECT:
4059 if (!chk->comment && comment)
4060 chk->comment = strdup(comment);
4061 /* fall though */
4062 case TCPCHK_ACT_ACTION_KW:
4063 free(comment);
4064 comment = NULL;
4065 break;
4066 case TCPCHK_ACT_SEND:
4067 case TCPCHK_ACT_EXPECT:
4068 if (!chk->comment && comment)
4069 chk->comment = strdup(comment);
4070 break;
4071 }
4072 }
4073 free(comment);
4074 comment = NULL;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004075
4076 out:
4077 return ret;
4078}
4079
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004080static int init_srv_check(struct server *srv)
4081{
4082 const char *err;
4083 struct tcpcheck_rule *r;
4084 int ret = 0;
4085
4086 if (!srv->do_check)
4087 goto out;
4088
4089
4090 /* If neither a port nor an addr was specified and no check transport
4091 * layer is forced, then the transport layer used by the checks is the
4092 * same as for the production traffic. Otherwise we use raw_sock by
4093 * default, unless one is specified.
4094 */
4095 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4096 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4097 srv->check.use_ssl = srv->use_ssl;
4098 srv->check.xprt = srv->xprt;
4099 }
4100 else if (srv->check.use_ssl == 1)
4101 srv->check.xprt = xprt_get(XPRT_SSL);
4102
4103 srv->check.send_proxy |= (srv->pp_opts);
4104 }
4105
4106 /* validate <srv> server health-check settings */
4107
4108 /* We need at least a service port, a check port or the first tcp-check
4109 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4110 */
4111 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4112 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4113 goto init;
4114
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004115 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004116 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4117 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4118 ret |= ERR_ALERT | ERR_ABORT;
4119 goto out;
4120 }
4121
4122 /* search the first action (connect / send / expect) in the list */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004123 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
Christopher Faulet5c288742020-03-31 08:15:58 +02004124 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004125 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4126 "nor tcp_check rule 'connect' with port information.\n",
4127 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4128 ret |= ERR_ALERT | ERR_ABORT;
4129 goto out;
4130 }
4131
4132 /* scan the tcp-check ruleset to ensure a port has been configured */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004133 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
Christopher Faulet5c288742020-03-31 08:15:58 +02004134 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004135 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4136 "and a tcp_check rule 'connect' with no port information.\n",
4137 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4138 ret |= ERR_ALERT | ERR_ABORT;
4139 goto out;
4140 }
4141 }
4142
4143 init:
4144 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4145 if (err) {
4146 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4147 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4148 ret |= ERR_ALERT | ERR_ABORT;
4149 goto out;
4150 }
4151 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4152 global.maxsock++;
4153
4154 out:
4155 return ret;
4156}
4157
4158static int init_srv_agent_check(struct server *srv)
4159{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004160 struct tcpcheck_rule *chk;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004161 const char *err;
4162 int ret = 0;
4163
4164 if (!srv->do_agent)
4165 goto out;
4166
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004167 /* If there is no connect rule preceeding all send / expect rules, an
4168 * implicit one is inserted before all others.
4169 */
4170 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4171 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4172 chk = calloc(1, sizeof(*chk));
4173 if (!chk) {
4174 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4175 " to agent-check for server '%s' (out of memory).\n",
4176 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4177 ret |= ERR_ALERT | ERR_FATAL;
4178 goto out;
4179 }
4180 chk->action = TCPCHK_ACT_CONNECT;
4181 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4182 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
4183 }
4184
4185
4186 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004187 if (err) {
4188 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4189 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4190 ret |= ERR_ALERT | ERR_ABORT;
4191 goto out;
4192 }
4193
4194 if (!srv->agent.inter)
4195 srv->agent.inter = srv->check.inter;
4196
4197 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4198 global.maxsock++;
4199
4200 out:
4201 return ret;
4202}
4203
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004204void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004205{
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02004206 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004207 px->tcpcheck_rules.flags = 0;
4208 px->tcpcheck_rules.list = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004209}
4210
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004211static void deinit_srv_check(struct server *srv)
4212{
Christopher Fauletce8111e2020-04-06 15:04:11 +02004213 if (srv->check.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004214 free_check(&srv->check);
Christopher Fauletce8111e2020-04-06 15:04:11 +02004215 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
4216 srv->do_check = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004217}
4218
4219
4220static void deinit_srv_agent_check(struct server *srv)
4221{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004222 if (srv->agent.tcpcheck_rules) {
4223 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
4224 free(srv->agent.tcpcheck_rules);
4225 srv->agent.tcpcheck_rules = NULL;
4226 }
4227
4228 if (srv->agent.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004229 free_check(&srv->agent);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004230
4231 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
4232 srv->do_agent = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004233}
4234
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004235static void deinit_tcpchecks()
4236{
4237 struct tcpcheck_ruleset *rs, *rsb;
4238 struct tcpcheck_rule *r, *rb;
4239
4240 list_for_each_entry_safe(rs, rsb, &tcpchecks_list, list) {
4241 LIST_DEL(&rs->list);
4242 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4243 LIST_DEL(&r->list);
4244 free_tcpcheck(r, 0);
4245 }
4246 free(rs->name);
4247 free(rs);
4248 }
4249}
4250
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004251
4252REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004253REGISTER_POST_SERVER_CHECK(init_srv_check);
4254REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
4255
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004256REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004257REGISTER_SERVER_DEINIT(deinit_srv_check);
4258REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004259REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004260
Christopher Faulet404f9192020-04-09 23:13:54 +02004261static struct tcpcheck_ruleset *tcpcheck_ruleset_lookup(const char *name)
4262{
4263 struct tcpcheck_ruleset *rs;
4264
4265 list_for_each_entry(rs, &tcpchecks_list, list) {
4266 if (strcmp(rs->name, name) == 0)
4267 return rs;
4268 }
4269 return NULL;
4270}
4271
4272static struct tcpcheck_ruleset *tcpcheck_ruleset_create(const char *name)
4273{
4274 struct tcpcheck_ruleset *rs;
4275
4276 rs = calloc(1, sizeof(*rs));
4277 if (rs == NULL)
4278 return NULL;
4279
4280 rs->name = strdup(name);
4281 if (rs->name == NULL) {
4282 free(rs);
4283 return NULL;
4284 }
4285
4286 LIST_INIT(&rs->list);
4287 LIST_INIT(&rs->rules);
4288 LIST_ADDQ(&tcpchecks_list, &rs->list);
4289 return rs;
4290}
4291
4292static void tcpcheck_ruleset_release(struct tcpcheck_ruleset *rs)
4293{
4294 struct tcpcheck_rule *r, *rb;
4295 if (!rs)
4296 return;
4297
4298 LIST_DEL(&rs->list);
4299 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4300 LIST_DEL(&r->list);
4301 free_tcpcheck(r, 0);
4302 }
4303 free(rs->name);
4304 free(rs);
4305}
4306
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004307/* extracts check payload at a fixed position and length */
4308static int
4309smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
4310{
4311 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
4312 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
4313 struct server *srv = (smp->sess ? objt_server(smp->sess->origin) : NULL);
4314 struct buffer *buf;
4315
4316 if (!srv || !srv->do_check)
4317 return 0;
4318
4319 buf = &srv->check.bi;
4320 if (buf_offset > b_data(buf))
4321 goto no_match;
4322 if (buf_offset + buf_size > b_data(buf))
4323 buf_size = 0;
4324
4325 /* init chunk as read only */
4326 smp->data.type = SMP_T_STR;
4327 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
4328 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
4329
4330 return 1;
4331
4332 no_match:
4333 smp->flags = 0;
4334 return 0;
4335}
4336
4337static struct sample_fetch_kw_list smp_kws = {ILH, {
4338 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
4339 { /* END */ },
4340}};
4341
4342INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
4343
4344
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004345struct action_kw_list tcp_check_keywords = {
4346 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
4347};
4348
4349/* Return the struct action_kw associated to a keyword */
4350static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
4351{
4352 return action_lookup(&tcp_check_keywords.list, kw);
4353}
4354
4355static void action_kw_tcp_check_build_list(struct buffer *chk)
4356{
4357 action_build_list(&tcp_check_keywords.list, chk);
4358}
4359
4360/* Create a tcp-check rule resulting from parsing a custom keyword. */
4361static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004362 struct list *rules, struct action_kw *kw,
4363 const char *file, int line, char **errmsg)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004364{
4365 struct tcpcheck_rule *chk = NULL;
4366 struct act_rule *actrule = NULL;
4367
4368 actrule = calloc(1, sizeof(*actrule));
4369 if (!actrule) {
4370 memprintf(errmsg, "out of memory");
4371 goto error;
4372 }
4373 actrule->kw = kw;
4374 actrule->from = ACT_F_TCP_CHK;
4375
4376 cur_arg++;
4377 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
4378 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
4379 goto error;
4380 }
4381
4382 chk = calloc(1, sizeof(*chk));
4383 if (!chk) {
4384 memprintf(errmsg, "out of memory");
4385 goto error;
4386 }
4387 chk->action = TCPCHK_ACT_ACTION_KW;
4388 chk->action_kw.rule = actrule;
4389 return chk;
4390
4391 error:
4392 free(actrule);
4393 return NULL;
4394}
4395
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004396static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Fauletb7d30092020-03-30 15:19:03 +02004397 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004398{
4399 struct tcpcheck_rule *chk = NULL;
Christopher Faulet5c288742020-03-31 08:15:58 +02004400 struct sockaddr_storage *sk = NULL;
Christopher Faulet98572322020-03-30 13:16:44 +02004401 char *comment = NULL, *sni = NULL, *alpn = NULL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004402 struct sample_expr *port_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004403 unsigned short conn_opts = 0;
4404 long port = 0;
Christopher Faulet98572322020-03-30 13:16:44 +02004405 int alpn_len = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004406
4407 list_for_each_entry(chk, rules, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004408 if (chk->action != TCPCHK_ACT_COMMENT && chk->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004409 break;
4410 }
4411 if (&chk->list != rules && chk->action != TCPCHK_ACT_CONNECT) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004412 memprintf(errmsg, "first step MUST also be a 'connect', "
4413 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
4414 "when there is a 'connect' step in the tcp-check ruleset");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004415 goto error;
4416 }
4417
4418 cur_arg++;
4419 while (*(args[cur_arg])) {
Christopher Fauletbb591a12020-04-01 16:52:17 +02004420 if (strcmp(args[cur_arg], "default") == 0)
4421 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
Christopher Faulet5c288742020-03-31 08:15:58 +02004422 else if (strcmp(args[cur_arg], "addr") == 0) {
4423 int port1, port2;
4424 struct protocol *proto;
4425
4426 if (!*(args[cur_arg+1])) {
4427 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
4428 goto error;
4429 }
4430
4431 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
4432 if (!sk) {
4433 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
4434 goto error;
4435 }
4436
4437 proto = protocol_by_family(sk->ss_family);
4438 if (!proto || !proto->connect) {
4439 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
4440 args[cur_arg]);
4441 goto error;
4442 }
4443
4444 if (port1 != port2) {
4445 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
4446 args[cur_arg], args[cur_arg+1]);
4447 goto error;
4448 }
4449
4450 cur_arg++;
4451 }
Christopher Faulet4dce5922020-03-30 13:54:42 +02004452 else if (strcmp(args[cur_arg], "port") == 0) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004453 const char *p, *end;
4454
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004455 if (!*(args[cur_arg+1])) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004456 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004457 goto error;
4458 }
4459 cur_arg++;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004460
4461 port = 0;
4462 release_sample_expr(port_expr);
4463 p = args[cur_arg]; end = p + strlen(p);
4464 port = read_uint(&p, end);
4465 if (p != end) {
4466 int idx = 0;
4467
4468 px->conf.args.ctx = ARGC_SRV;
4469 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4470 file, line, errmsg, &px->conf.args, NULL);
4471
4472 if (!port_expr) {
4473 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
4474 goto error;
4475 }
4476 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4477 memprintf(errmsg, "error detected while parsing port expression : "
4478 " fetch method '%s' extracts information from '%s', "
4479 "none of which is available here.\n",
4480 args[cur_arg], sample_src_names(port_expr->fetch->use));
4481 goto error;
4482 }
4483 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
4484 }
4485 else if (port > 65535 || port < 1) {
4486 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
4487 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004488 goto error;
4489 }
4490 }
4491 else if (strcmp(args[cur_arg], "comment") == 0) {
4492 if (!*(args[cur_arg+1])) {
4493 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4494 goto error;
4495 }
4496 cur_arg++;
4497 free(comment);
4498 comment = strdup(args[cur_arg]);
4499 if (!comment) {
4500 memprintf(errmsg, "out of memory");
4501 goto error;
4502 }
4503 }
4504 else if (strcmp(args[cur_arg], "send-proxy") == 0)
4505 conn_opts |= TCPCHK_OPT_SEND_PROXY;
Christopher Faulet085426a2020-03-30 13:07:02 +02004506 else if (strcmp(args[cur_arg], "via-socks4") == 0)
4507 conn_opts |= TCPCHK_OPT_SOCKS4;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004508 else if (strcmp(args[cur_arg], "linger") == 0)
4509 conn_opts |= TCPCHK_OPT_LINGER;
4510#ifdef USE_OPENSSL
4511 else if (strcmp(args[cur_arg], "ssl") == 0) {
4512 px->options |= PR_O_TCPCHK_SSL;
4513 conn_opts |= TCPCHK_OPT_SSL;
4514 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02004515 else if (strcmp(args[cur_arg], "sni") == 0) {
4516 if (!*(args[cur_arg+1])) {
4517 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4518 goto error;
4519 }
4520 cur_arg++;
4521 free(sni);
4522 sni = strdup(args[cur_arg]);
4523 if (!sni) {
4524 memprintf(errmsg, "out of memory");
4525 goto error;
4526 }
4527 }
Christopher Faulet98572322020-03-30 13:16:44 +02004528 else if (strcmp(args[cur_arg], "alpn") == 0) {
4529#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
4530 free(alpn);
4531 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
4532 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
4533 goto error;
4534 }
4535 cur_arg++;
4536#else
4537 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
4538 goto error;
4539#endif
4540 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004541#endif /* USE_OPENSSL */
4542
4543 else {
Christopher Faulet5c288742020-03-31 08:15:58 +02004544 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004545#ifdef USE_OPENSSL
Christopher Faulet98572322020-03-30 13:16:44 +02004546 ", 'ssl', 'sni', 'alpn'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004547#endif /* USE_OPENSSL */
Christopher Faulet4dce5922020-03-30 13:54:42 +02004548 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004549 args[cur_arg]);
4550 goto error;
4551 }
4552 cur_arg++;
4553 }
4554
4555 chk = calloc(1, sizeof(*chk));
4556 if (!chk) {
4557 memprintf(errmsg, "out of memory");
4558 goto error;
4559 }
Gaetan Rivet06d963a2020-02-21 18:49:05 +01004560 chk->action = TCPCHK_ACT_CONNECT;
4561 chk->comment = comment;
4562 chk->connect.port = port;
4563 chk->connect.options = conn_opts;
Christopher Faulet79b31d42020-03-30 13:00:05 +02004564 chk->connect.sni = sni;
Christopher Faulet98572322020-03-30 13:16:44 +02004565 chk->connect.alpn = alpn;
4566 chk->connect.alpn_len= alpn_len;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004567 chk->connect.port_expr= port_expr;
Christopher Faulet5c288742020-03-31 08:15:58 +02004568 if (sk)
4569 chk->connect.addr = *sk;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004570 return chk;
4571
4572 error:
Christopher Faulet98572322020-03-30 13:16:44 +02004573 free(alpn);
Christopher Faulet79b31d42020-03-30 13:00:05 +02004574 free(sni);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004575 free(comment);
Christopher Fauletb7d30092020-03-30 15:19:03 +02004576 release_sample_expr(port_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004577 return NULL;
4578}
4579
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004580static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004581 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004582{
4583 struct tcpcheck_rule *chk = NULL;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004584 char *comment = NULL, *data = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004585 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004586
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004587 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004588 if (!*(args[cur_arg+1])) {
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004589 memprintf(errmsg, "'%s' expects a %s as argument",
4590 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004591 goto error;
4592 }
4593
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004594 data = args[cur_arg+1];
4595
4596 cur_arg += 2;
4597 while (*(args[cur_arg])) {
4598 if (strcmp(args[cur_arg], "comment") == 0) {
4599 if (!*(args[cur_arg+1])) {
4600 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4601 goto error;
4602 }
4603 cur_arg++;
4604 free(comment);
4605 comment = strdup(args[cur_arg]);
4606 if (!comment) {
4607 memprintf(errmsg, "out of memory");
4608 goto error;
4609 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004610 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004611 else if (strcmp(args[cur_arg], "log-format") == 0) {
4612 if (type == TCPCHK_SEND_BINARY)
4613 type = TCPCHK_SEND_BINARY_LF;
4614 else if (type == TCPCHK_SEND_STRING)
4615 type = TCPCHK_SEND_STRING_LF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004616 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004617 else {
4618 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
4619 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004620 goto error;
4621 }
4622 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004623 }
4624
4625 chk = calloc(1, sizeof(*chk));
4626 if (!chk) {
4627 memprintf(errmsg, "out of memory");
4628 goto error;
4629 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004630 chk->action = TCPCHK_ACT_SEND;
4631 chk->comment = comment;
4632 chk->send.type = type;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004633
4634 switch (chk->send.type) {
4635 case TCPCHK_SEND_STRING:
4636 chk->send.data = ist2(strdup(data), strlen(data));
4637 if (!isttest(chk->send.data)) {
4638 memprintf(errmsg, "out of memory");
4639 goto error;
4640 }
4641 break;
4642 case TCPCHK_SEND_BINARY:
4643 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
4644 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
4645 goto error;
4646 }
4647 break;
4648 case TCPCHK_SEND_STRING_LF:
4649 case TCPCHK_SEND_BINARY_LF:
4650 LIST_INIT(&chk->send.fmt);
4651 px->conf.args.ctx = ARGC_SRV;
4652 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4653 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
4654 goto error;
4655 }
4656 break;
4657 case TCPCHK_SEND_UNDEF:
4658 goto error;
4659 }
4660
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004661 return chk;
4662
4663 error:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004664 free(chk);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004665 free(comment);
4666 return NULL;
4667}
4668
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004669static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
4670 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004671{
4672 struct tcpcheck_rule *chk = NULL;
4673 char *comment = NULL;
4674
4675 if (!*(args[cur_arg+1])) {
4676 memprintf(errmsg, "expects a string as argument");
4677 goto error;
4678 }
4679 cur_arg++;
4680 comment = strdup(args[cur_arg]);
4681 if (!comment) {
4682 memprintf(errmsg, "out of memory");
4683 goto error;
4684 }
4685
4686 chk = calloc(1, sizeof(*chk));
4687 if (!chk) {
4688 memprintf(errmsg, "out of memory");
4689 goto error;
4690 }
4691 chk->action = TCPCHK_ACT_COMMENT;
4692 chk->comment = comment;
4693 return chk;
4694
4695 error:
4696 free(comment);
4697 return NULL;
4698}
4699
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004700static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px, struct list *rules,
4701 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004702{
4703 struct tcpcheck_rule *prev_check, *chk = NULL;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004704 struct sample_expr *status_expr = NULL;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004705 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004706 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
Christopher Fauletec07e382020-04-07 14:56:26 +02004707 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004708 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
4709 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004710 long min_recv = -1;
4711 int inverse = 0, with_capture = 0;
4712
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004713 str = on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004714 if (!*(args[cur_arg+1])) {
4715 memprintf(errmsg, "expects at least a matching pattern as arguments");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004716 goto error;
4717 }
4718
4719 cur_arg++;
4720 while (*(args[cur_arg])) {
4721 int in_pattern = 0;
4722
4723 rescan:
4724 if (strcmp(args[cur_arg], "min-recv") == 0) {
4725 if (in_pattern) {
4726 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4727 goto error;
4728 }
4729 if (!*(args[cur_arg+1])) {
4730 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
4731 goto error;
4732 }
4733 /* Use an signed integer here because of chksize */
4734 cur_arg++;
4735 min_recv = atol(args[cur_arg]);
4736 if (min_recv < -1 || min_recv > INT_MAX) {
4737 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
4738 goto error;
4739 }
4740 }
4741 else if (*(args[cur_arg]) == '!') {
4742 in_pattern = 1;
4743 while (*(args[cur_arg]) == '!') {
4744 inverse = !inverse;
4745 args[cur_arg]++;
4746 }
4747 if (!*(args[cur_arg]))
4748 cur_arg++;
4749 goto rescan;
4750 }
4751 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "binary") == 0 ||
4752 strcmp(args[cur_arg], "rstring") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
4753 if (type != TCPCHK_EXPECT_UNDEF) {
4754 memprintf(errmsg, "only on pattern expected");
4755 goto error;
4756 }
4757 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING :
4758 ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY :
4759 ((*(args[cur_arg]+1) == 's') ? TCPCHK_EXPECT_REGEX : TCPCHK_EXPECT_REGEX_BINARY)));
4760
4761 if (!*(args[cur_arg+1])) {
4762 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4763 goto error;
4764 }
4765 cur_arg++;
4766 pattern = args[cur_arg];
4767 }
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004768 else if (strcmp(args[cur_arg], "custom") == 0) {
4769 if (in_pattern) {
4770 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4771 goto error;
4772 }
4773 if (type != TCPCHK_EXPECT_UNDEF) {
4774 memprintf(errmsg, "only on pattern expected");
4775 goto error;
4776 }
4777 type = TCPCHK_EXPECT_CUSTOM;
4778 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004779 else if (strcmp(args[cur_arg], "comment") == 0) {
4780 if (in_pattern) {
4781 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4782 goto error;
4783 }
4784 if (!*(args[cur_arg+1])) {
4785 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4786 goto error;
4787 }
4788 cur_arg++;
4789 free(comment);
4790 comment = strdup(args[cur_arg]);
4791 if (!comment) {
4792 memprintf(errmsg, "out of memory");
4793 goto error;
4794 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004795 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004796 else if (strcmp(args[cur_arg], "on-success") == 0) {
4797 if (in_pattern) {
4798 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4799 goto error;
4800 }
4801 if (!*(args[cur_arg+1])) {
4802 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4803 goto error;
4804 }
4805 cur_arg++;
4806 free(on_success_msg);
4807 on_success_msg = strdup(args[cur_arg]);
4808 if (!on_success_msg) {
4809 memprintf(errmsg, "out of memory");
4810 goto error;
4811 }
4812 }
4813 else if (strcmp(args[cur_arg], "on-error") == 0) {
4814 if (in_pattern) {
4815 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4816 goto error;
4817 }
4818 if (!*(args[cur_arg+1])) {
4819 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4820 goto error;
4821 }
4822 cur_arg++;
4823 free(on_error_msg);
4824 on_error_msg = strdup(args[cur_arg]);
4825 if (!on_error_msg) {
4826 memprintf(errmsg, "out of memory");
4827 goto error;
4828 }
Christopher Fauletec07e382020-04-07 14:56:26 +02004829 }
4830 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4831 if (in_pattern) {
4832 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4833 goto error;
4834 }
4835 if (!*(args[cur_arg+1])) {
4836 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4837 goto error;
4838 }
4839 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4840 ok_st = HCHK_STATUS_L7OKD;
4841 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4842 ok_st = HCHK_STATUS_L7OKCD;
4843 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4844 ok_st = HCHK_STATUS_L6OK;
4845 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4846 ok_st = HCHK_STATUS_L4OK;
4847 else {
4848 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4849 args[cur_arg], args[cur_arg+1]);
4850 goto error;
4851 }
4852 cur_arg++;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004853 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004854 else if (strcmp(args[cur_arg], "error-status") == 0) {
4855 if (in_pattern) {
4856 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4857 goto error;
4858 }
4859 if (!*(args[cur_arg+1])) {
4860 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4861 goto error;
4862 }
4863 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4864 err_st = HCHK_STATUS_L7RSP;
4865 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4866 err_st = HCHK_STATUS_L7STS;
4867 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4868 err_st = HCHK_STATUS_L6RSP;
4869 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4870 err_st = HCHK_STATUS_L4CON;
4871 else {
4872 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4873 args[cur_arg], args[cur_arg+1]);
4874 goto error;
4875 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004876 cur_arg++;
4877 }
4878 else if (strcmp(args[cur_arg], "status-code") == 0) {
4879 int idx = 0;
4880
4881 if (in_pattern) {
4882 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4883 goto error;
4884 }
4885 if (!*(args[cur_arg+1])) {
4886 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4887 goto error;
4888 }
4889
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004890 cur_arg++;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004891 release_sample_expr(status_expr);
4892 px->conf.args.ctx = ARGC_SRV;
4893 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4894 file, line, errmsg, &px->conf.args, NULL);
4895 if (!status_expr) {
4896 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4897 goto error;
4898 }
4899 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4900 memprintf(errmsg, "error detected while parsing status-code expression : "
4901 " fetch method '%s' extracts information from '%s', "
4902 "none of which is available here.\n",
4903 args[cur_arg], sample_src_names(status_expr->fetch->use));
4904 goto error;
4905 }
4906 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004907 }
4908 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4909 if (in_pattern) {
4910 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4911 goto error;
4912 }
4913 if (!*(args[cur_arg+1])) {
4914 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4915 goto error;
4916 }
4917 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4918 tout_st = HCHK_STATUS_L7TOUT;
4919 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4920 tout_st = HCHK_STATUS_L6TOUT;
4921 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4922 tout_st = HCHK_STATUS_L4TOUT;
4923 else {
4924 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4925 args[cur_arg], args[cur_arg+1]);
4926 goto error;
4927 }
4928 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004929 }
4930 else {
4931 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4932 " or comment but got '%s' as argument.", args[cur_arg]);
4933 goto error;
4934 }
4935
4936 cur_arg++;
4937 }
4938
4939 if (comment) {
4940 char *p = comment;
4941
4942 while (*p) {
4943 if (*p == '\\') {
4944 p++;
4945 if (!*p || !isdigit((unsigned char)*p) ||
4946 (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
4947 memprintf(errmsg, "invalid backreference in 'comment' argument");
4948 goto error;
4949 }
4950 with_capture = 1;
4951 }
4952 p++;
4953 }
4954 if (with_capture && !inverse)
4955 memprintf(errmsg, "using backreference in a positive expect comment is useless");
4956 }
4957
4958 chk = calloc(1, sizeof(*chk));
4959 if (!chk) {
4960 memprintf(errmsg, "out of memory");
4961 goto error;
4962 }
4963 chk->action = TCPCHK_ACT_EXPECT;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004964 LIST_INIT(&chk->expect.onerror_fmt);
4965 LIST_INIT(&chk->expect.onsuccess_fmt);
4966 chk->comment = comment; comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004967 chk->expect.type = type;
4968 chk->expect.min_recv = min_recv;
4969 chk->expect.inverse = inverse;
4970 chk->expect.with_capture = with_capture;
Christopher Fauletec07e382020-04-07 14:56:26 +02004971 chk->expect.ok_status = ok_st;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004972 chk->expect.err_status = err_st;
4973 chk->expect.tout_status = tout_st;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004974 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004975
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004976 if (on_success_msg) {
4977 px->conf.args.ctx = ARGC_SRV;
4978 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4979 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4980 goto error;
4981 }
4982 free(on_success_msg);
4983 on_success_msg = NULL;
4984 }
4985 if (on_error_msg) {
4986 px->conf.args.ctx = ARGC_SRV;
4987 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4988 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4989 goto error;
4990 }
4991 free(on_error_msg);
4992 on_error_msg = NULL;
4993 }
4994
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004995 switch (chk->expect.type) {
4996 case TCPCHK_EXPECT_STRING:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02004997 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
4998 if (!chk->expect.data.ptr) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004999 memprintf(errmsg, "out of memory");
5000 goto error;
5001 }
5002 break;
5003 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02005004 if (parse_binary(pattern, &chk->expect.data.ptr, (int *)&chk->expect.data.len, errmsg) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005005 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
5006 goto error;
5007 }
5008 case TCPCHK_EXPECT_REGEX:
5009 case TCPCHK_EXPECT_REGEX_BINARY:
5010 chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
5011 if (!chk->expect.regex)
5012 goto error;
5013 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02005014 case TCPCHK_EXPECT_CUSTOM:
5015 chk->expect.custom = NULL; /* Must be defined by the caller ! */
5016 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005017 case TCPCHK_EXPECT_UNDEF:
5018 free(chk);
5019 memprintf(errmsg, "pattern not found");
5020 goto error;
5021 }
5022
5023 /* All tcp-check expect points back to the first inverse expect rule in
5024 * a chain of one or more expect rule, potentially itself.
5025 */
5026 chk->expect.head = chk;
5027 list_for_each_entry_rev(prev_check, rules, list) {
5028 if (prev_check->action == TCPCHK_ACT_EXPECT) {
5029 if (prev_check->expect.inverse)
5030 chk->expect.head = prev_check;
5031 continue;
5032 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01005033 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005034 break;
5035 }
5036 return chk;
5037
5038 error:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005039 free_tcpcheck(chk, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005040 free(str);
5041 free(comment);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005042 free(on_success_msg);
5043 free(on_error_msg);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005044 release_sample_expr(status_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005045 return NULL;
5046}
5047
5048/* Parses the "tcp-check" proxy keyword */
5049static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5050 struct proxy *defpx, const char *file, int line,
5051 char **errmsg)
5052{
Christopher Faulet404f9192020-04-09 23:13:54 +02005053 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005054 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005055 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005056
5057 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5058 ret = 1;
5059
Christopher Faulet404f9192020-04-09 23:13:54 +02005060 /* Deduce the ruleset name from the proxy info */
5061 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5062 ((curpx == defpx) ? "defaults" : curpx->id),
5063 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005064
Christopher Faulet404f9192020-04-09 23:13:54 +02005065 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5066 if (rs == NULL) {
5067 rs = tcpcheck_ruleset_create(b_orig(&trash));
5068 if (rs == NULL) {
5069 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005070 goto error;
5071 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005072 }
5073
Gaetan Rivet5301b012020-02-25 17:19:17 +01005074 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005075 if (!LIST_ISEMPTY(&rs->rules)) {
5076 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005077 index = chk->index + 1;
5078 }
5079
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005080 cur_arg = 1;
5081 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005082 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005083 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005084 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005085 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005086 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005087 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005088 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005089 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005090 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5091
5092 if (!kw) {
5093 action_kw_tcp_check_build_list(&trash);
5094 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5095 "%s%s. but got '%s'",
5096 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5097 goto error;
5098 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005099 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005100 }
5101
5102 if (!chk) {
5103 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5104 goto error;
5105 }
5106 ret = (*errmsg != NULL); /* Handle warning */
5107
5108 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005109 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005110 LIST_ADDQ(&rs->rules, &chk->list);
5111
5112 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5113 !(curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK)) {
5114 /* Use this ruleset if the proxy already has tcp-check enabled */
5115 curpx->tcpcheck_rules.list = &rs->rules;
5116 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5117 }
5118 else {
5119 /* mark this ruleset as unused for now */
5120 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5121 }
5122
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005123 return ret;
5124
5125 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005126 free_tcpcheck(chk, 0);
5127 tcpcheck_ruleset_release(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005128 return -1;
5129}
5130
Christopher Faulet51b129f2020-04-09 15:54:18 +02005131/* Parses the "http-check" proxy keyword */
5132static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5133 struct proxy *defpx, const char *file, int line,
5134 char **errmsg)
5135{
5136 int cur_arg, ret = 0;
5137
5138 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5139 ret = 1;
5140
5141 cur_arg = 1;
5142 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5143 /* enable a graceful server shutdown on an HTTP 404 response */
5144 curpx->options |= PR_O_DISABLE404;
5145 if (too_many_args(1, args, errmsg, NULL))
5146 goto error;
5147 }
5148 else if (strcmp(args[cur_arg], "send-state") == 0) {
5149 /* enable emission of the apparent state of a server in HTTP checks */
5150 curpx->options2 |= PR_O2_CHK_SNDST;
5151 if (too_many_args(1, args, errmsg, NULL))
5152 goto error;
5153 }
5154 else if (strcmp(args[cur_arg], "send") == 0) {
5155 free(curpx->check_hdrs);
5156 free(curpx->check_body);
5157 curpx->check_hdrs = curpx->check_body = NULL;
5158 curpx->check_hdrs_len = curpx->check_body_len = 0;
5159
5160 cur_arg++;
5161 while (*(args[cur_arg])) {
5162 if (strcmp(args[cur_arg], "hdr") == 0) {
5163 int hdr_len;
5164 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2])) {
5165 memprintf(errmsg, "'%s %s' : %s expects a name and a value as parameter.",
5166 args[0], args[1], args[cur_arg]);
5167 goto error;
5168 }
5169
5170 cur_arg++;
5171 hdr_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 4;
5172 curpx->check_hdrs = my_realloc2(curpx->check_hdrs, curpx->check_hdrs_len+hdr_len+1);
5173 if (curpx->check_hdrs == NULL) {
5174 memprintf(errmsg, "out of memory.");
5175 goto error;
5176 }
5177 snprintf(curpx->check_hdrs + curpx->check_hdrs_len, hdr_len+1, "%s: %s\r\n", args[cur_arg], args[cur_arg+1]);
5178 curpx->check_hdrs_len += hdr_len;
5179
5180 cur_arg++;
5181 }
5182 else if (strcmp(args[cur_arg], "body") == 0) {
5183 if (!*(args[cur_arg+1])) {
5184 memprintf(errmsg, "'%s %s' : %s expects a string as parameter.",
5185 args[0], args[1], args[cur_arg]);
5186 goto error;
5187 }
5188 cur_arg++;
5189 free(curpx->check_body);
5190 curpx->check_body = strdup(args[cur_arg]);
5191 curpx->check_body_len = strlen(args[cur_arg]);
5192 if (curpx->check_body == NULL) {
5193 memprintf(errmsg, "out of memory.");
5194 goto error;
5195 }
5196 }
5197 else {
5198 memprintf(errmsg, "'%s %s' only supports 'hdr' and 'body', found '%s'.",
5199 args[0], args[1], args[cur_arg]);
5200 goto error;
5201 }
5202 cur_arg++;
5203 }
5204 }
5205 else if (strcmp(args[cur_arg], "expect") == 0) {
5206 const char *ptr_arg;
5207
5208 if (curpx->options2 & PR_O2_EXP_TYPE) {
5209 memprintf(errmsg, "'%s %s' already specified.", args[0], args[1]);
5210 goto error;
5211 }
5212
5213 cur_arg++;
5214
5215 /* consider exclamation marks, sole or at the beginning of a word */
5216 while (*(ptr_arg = args[cur_arg])) {
5217 while (*ptr_arg == '!') {
5218 curpx->options2 ^= PR_O2_EXP_INV;
5219 ptr_arg++;
5220 }
5221 if (*ptr_arg)
5222 break;
5223 cur_arg++;
5224 }
5225
5226 /* now ptr_arg points to the beginning of a word past any possible
5227 * exclamation mark, and cur_arg is the argument which holds this word.
5228 */
5229 if (strcmp(ptr_arg, "status") == 0) {
5230 if (!*(args[cur_arg+1])) {
5231 memprintf(errmsg, "'%s %s %s' expects <string> as an argument.",
5232 args[0], args[1], ptr_arg);
5233 goto error;
5234 }
5235 curpx->options2 |= PR_O2_EXP_STS;
5236 free(curpx->expect_str);
5237 curpx->expect_str = strdup(args[cur_arg+1]);
5238 }
5239 else if (strcmp(ptr_arg, "string") == 0) {
5240 if (!*(args[cur_arg+1])) {
5241 memprintf(errmsg, "'%s %s %s' expects <string> as an argument.",
5242 args[0], args[1], ptr_arg);
5243 goto error;
5244 }
5245 curpx->options2 |= PR_O2_EXP_STR;
5246 free(curpx->expect_str);
5247 curpx->expect_str = strdup(args[cur_arg+1]);
5248 }
5249 else if (strcmp(ptr_arg, "rstatus") == 0) {
5250 if (!*(args[cur_arg+1])) {
5251 memprintf(errmsg, "'%s %s %s' expects <regex> as an argument.",
5252 args[0], args[1], ptr_arg);
5253 goto error;
5254 }
5255 curpx->options2 |= PR_O2_EXP_RSTS;
5256 free(curpx->expect_str);
5257 regex_free(curpx->expect_regex);
5258 curpx->expect_str = strdup(args[cur_arg+1]);
5259 if (!(curpx->expect_regex = regex_comp(args[cur_arg+1], 1, 1, errmsg))) {
5260 memprintf(errmsg, "'%s %s %s' : regular expression '%s': %s.",
5261 args[0], args[1], ptr_arg, args[cur_arg+1], *errmsg);
5262 goto error;
5263 }
5264 }
5265 else if (strcmp(ptr_arg, "rstring") == 0) {
5266 if (!*(args[cur_arg+1])) {
5267 memprintf(errmsg, "'%s %s %s' expects <regex> as an argument.",
5268 args[0], args[1], ptr_arg);
5269 goto error;
5270 }
5271 curpx->options2 |= PR_O2_EXP_RSTR;
5272 free(curpx->expect_str);
5273 regex_free(curpx->expect_regex);
5274 curpx->expect_str = strdup(args[cur_arg+1]);
5275 if (!(curpx->expect_regex = regex_comp(args[cur_arg+1], 1, 1, errmsg))) {
5276 memprintf(errmsg, "'%s %s %s' : regular expression '%s': %s.",
5277 args[0], args[1], ptr_arg, args[cur_arg + 1], *errmsg);
5278 goto error;
5279 }
5280 }
5281 else {
5282 memprintf(errmsg, "'%s %s' only supports [!] 'status', 'string', 'rstatus', 'rstring', found '%s'.",
5283 args[0], args[1], ptr_arg);
5284 goto error;
5285 }
5286 }
5287 else {
5288 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send', 'send-state' and 'expect'. but got '%s'.",
5289 args[0], args[1]);
5290 goto error;
5291 }
5292
5293 ret = (*errmsg != NULL); /* Handle warning */
5294 return ret;
5295
5296 error:
5297 return -1;
5298}
5299
Christopher Faulete9111b62020-04-09 18:12:08 +02005300/* Parses the "external-check" proxy keyword */
5301static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5302 struct proxy *defpx, const char *file, int line,
5303 char **errmsg)
5304{
5305 int cur_arg, ret = 0;
5306
5307 cur_arg = 1;
5308 if (!*(args[cur_arg])) {
5309 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5310 goto error;
5311 }
5312
5313 if (strcmp(args[cur_arg], "command") == 0) {
5314 if (too_many_args(2, args, errmsg, NULL))
5315 goto error;
5316 if (!*(args[cur_arg+1])) {
5317 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5318 goto error;
5319 }
5320 free(curpx->check_command);
5321 curpx->check_command = strdup(args[cur_arg+1]);
5322 }
5323 else if (strcmp(args[cur_arg], "path") == 0) {
5324 if (too_many_args(2, args, errmsg, NULL))
5325 goto error;
5326 if (!*(args[cur_arg+1])) {
5327 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5328 goto error;
5329 }
5330 free(curpx->check_path);
5331 curpx->check_path = strdup(args[cur_arg+1]);
5332 }
5333 else {
5334 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5335 args[0], args[1]);
5336 goto error;
5337 }
5338
5339 ret = (*errmsg != NULL); /* Handle warning */
5340 return ret;
5341
5342error:
5343 return -1;
5344}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005345
Christopher Faulet430e4802020-04-09 15:28:16 +02005346/* Parses the "option tcp-check" proxy keyword */
5347int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5348 const char *file, int line)
5349{
Christopher Faulet404f9192020-04-09 23:13:54 +02005350 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005351 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5352 int err_code = 0;
5353
5354 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5355 err_code |= ERR_WARN;
5356
5357 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5358 goto out;
5359
Christopher Faulet404f9192020-04-09 23:13:54 +02005360 curpx->options2 &= ~PR_O2_CHK_ANY;
5361 curpx->options2 |= PR_O2_TCPCHK_CHK;
5362
5363 if (!(rules->flags & TCPCHK_RULES_PROTO_CHK)) {
5364 /* If a tcp-check rulesset is already set, do nothing */
5365 if (rules->list)
5366 goto out;
5367
5368 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5369 * get it.
5370 */
5371 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5372 goto curpx_ruleset;
5373
5374 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5375 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5376 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5377 if (rs)
5378 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005379 }
5380
Christopher Faulet404f9192020-04-09 23:13:54 +02005381 curpx_ruleset:
5382 /* Deduce the ruleset name from the proxy info */
5383 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5384 ((curpx == defpx) ? "defaults" : curpx->id),
5385 curpx->conf.file, curpx->conf.line);
5386
5387 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5388 if (rs == NULL) {
5389 rs = tcpcheck_ruleset_create(b_orig(&trash));
5390 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005391 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5392 goto error;
5393 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005394 }
5395
Christopher Faulet404f9192020-04-09 23:13:54 +02005396 ruleset_found:
5397 free_tcpcheck_vars(&rules->preset_vars);
5398 rules->list = NULL;
5399 rules->flags = 0;
5400
Christopher Faulet430e4802020-04-09 15:28:16 +02005401 free(curpx->check_req);
5402 curpx->check_req = NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02005403
5404 rules->list = &rs->rules;
Christopher Faulet430e4802020-04-09 15:28:16 +02005405
5406 out:
5407 return err_code;
5408
5409 error:
5410 err_code |= ERR_ALERT | ERR_FATAL;
5411 goto out;
5412}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005413
5414/* Parses the "option redis-check" proxy keyword */
5415int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5416 const char *file, int line)
5417{
5418 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5419 static char *redis_res = "+PONG\r\n";
5420
5421 struct tcpcheck_ruleset *rs = NULL;
5422 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5423 struct tcpcheck_rule *chk;
5424 char *errmsg = NULL;
5425 int err_code = 0;
5426
5427 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5428 err_code |= ERR_WARN;
5429
5430 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5431 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005432
5433 curpx->options2 &= ~PR_O2_CHK_ANY;
5434 curpx->options2 |= PR_O2_TCPCHK_CHK;
5435
5436 free_tcpcheck_vars(&rules->preset_vars);
5437 rules->list = NULL;
5438 rules->flags = 0;
5439
5440 rs = tcpcheck_ruleset_lookup("*redis-check");
5441 if (rs)
5442 goto ruleset_found;
5443
5444 rs = tcpcheck_ruleset_create("*redis-check");
5445 if (rs == NULL) {
5446 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5447 goto error;
5448 }
5449
5450 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5451 1, curpx, &rs->rules, file, line, &errmsg);
5452 if (!chk) {
5453 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5454 goto error;
5455 }
5456 chk->index = 0;
5457 LIST_ADDQ(&rs->rules, &chk->list);
5458
5459 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5460 "error-status", "L7STS",
5461 "on-error", "%[check.payload(),cut_crlf]",
5462 "on-success", "Redis server is ok",
5463 ""},
5464 1, curpx, &rs->rules, file, line, &errmsg);
5465 if (!chk) {
5466 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5467 goto error;
5468 }
5469 chk->index = 1;
5470 LIST_ADDQ(&rs->rules, &chk->list);
5471
5472 LIST_ADDQ(&tcpchecks_list, &rs->list);
5473
5474 ruleset_found:
5475 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005476 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005477
5478 out:
5479 free(errmsg);
5480 return err_code;
5481
5482 error:
5483 tcpcheck_ruleset_release(rs);
5484 err_code |= ERR_ALERT | ERR_FATAL;
5485 goto out;
5486}
5487
Christopher Faulet811f78c2020-04-01 11:10:27 +02005488
5489/* Parses the "option ssl-hello-chk" proxy keyword */
5490int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5491 const char *file, int line)
5492{
5493 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5494 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5495 *
5496 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5497 */
5498 static char sslv3_client_hello[] = {
5499 "16" /* ContentType : 0x16 = Hanshake */
5500 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5501 "0079" /* ContentLength : 0x79 bytes after this one */
5502 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5503 "000075" /* HandshakeLength : 0x75 bytes after this one */
5504 "0300" /* Hello Version : 0x0300 = v3 */
5505 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5506 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5507 "00" /* Session ID length : empty (no session ID) */
5508 "004E" /* Cipher Suite Length : 78 bytes after this one */
5509 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5510 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5511 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5512 "000D" "000E" "000F" "0010" /* various bit lengths, */
5513 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5514 "0015" "0016" "0017" "0018"
5515 "0019" "001A" "001B" "002F"
5516 "0030" "0031" "0032" "0033"
5517 "0034" "0035" "0036" "0037"
5518 "0038" "0039" "003A"
5519 "01" /* Compression Length : 0x01 = 1 byte for types */
5520 "00" /* Compression Type : 0x00 = NULL compression */
5521 };
5522
5523 struct tcpcheck_ruleset *rs = NULL;
5524 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5525 struct tcpcheck_rule *chk;
5526 char *errmsg = NULL;
5527 int err_code = 0;
5528
5529 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5530 err_code |= ERR_WARN;
5531
5532 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5533 goto out;
5534
Christopher Faulet811f78c2020-04-01 11:10:27 +02005535 curpx->options2 &= ~PR_O2_CHK_ANY;
5536 curpx->options2 |= PR_O2_TCPCHK_CHK;
5537
5538 free_tcpcheck_vars(&rules->preset_vars);
5539 rules->list = NULL;
5540 rules->flags = 0;
5541
5542 rs = tcpcheck_ruleset_lookup("*ssl-hello-check");
5543 if (rs)
5544 goto ruleset_found;
5545
5546 rs = tcpcheck_ruleset_create("*ssl-hello-check");
5547 if (rs == NULL) {
5548 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5549 goto error;
5550 }
5551
5552 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5553 1, curpx, &rs->rules, file, line, &errmsg);
5554 if (!chk) {
5555 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5556 goto error;
5557 }
5558 chk->index = 0;
5559 LIST_ADDQ(&rs->rules, &chk->list);
5560
5561 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005562 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005563 "error-status", "L6RSP", "tout-status", "L6TOUT",
5564 ""},
5565 1, curpx, &rs->rules, file, line, &errmsg);
5566 if (!chk) {
5567 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5568 goto error;
5569 }
5570 chk->index = 1;
5571 LIST_ADDQ(&rs->rules, &chk->list);
5572
5573 LIST_ADDQ(&tcpchecks_list, &rs->list);
5574
5575 ruleset_found:
5576 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005577 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005578
5579 out:
5580 free(errmsg);
5581 return err_code;
5582
5583 error:
5584 tcpcheck_ruleset_release(rs);
5585 err_code |= ERR_ALERT | ERR_FATAL;
5586 goto out;
5587}
5588
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005589/* Parses the "option smtpchk" proxy keyword */
5590int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5591 const char *file, int line)
5592{
5593 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5594
5595 struct tcpcheck_ruleset *rs = NULL;
5596 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5597 struct tcpcheck_rule *chk;
5598 struct tcpcheck_var *var = NULL;
5599 char *cmd = NULL, *errmsg = NULL;
5600 int err_code = 0;
5601
5602 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5603 err_code |= ERR_WARN;
5604
5605 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5606 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005607
5608 curpx->options2 &= ~PR_O2_CHK_ANY;
5609 curpx->options2 |= PR_O2_TCPCHK_CHK;
5610
5611 free_tcpcheck_vars(&rules->preset_vars);
5612 rules->list = NULL;
5613 rules->flags = 0;
5614
5615 cur_arg += 2;
5616 if (*args[cur_arg] && *args[cur_arg+1] &&
5617 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5618 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5619 if (cmd)
5620 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
5621 }
5622 else {
5623 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
5624 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
5625 cmd = strdup("HELO localhost");
5626 }
5627
5628 var = tcpcheck_var_create("check.smtp_cmd");
5629 if (cmd == NULL || var == NULL) {
5630 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5631 goto error;
5632 }
5633 var->data.type = SMP_T_STR;
5634 var->data.u.str.area = cmd;
5635 var->data.u.str.data = strlen(cmd);
5636 LIST_INIT(&var->list);
5637 LIST_ADDQ(&rules->preset_vars, &var->list);
5638 cmd = NULL;
5639 var = NULL;
5640
5641 rs = tcpcheck_ruleset_lookup("*smtp-check");
5642 if (rs)
5643 goto ruleset_found;
5644
5645 rs = tcpcheck_ruleset_create("*smtp-check");
5646 if (rs == NULL) {
5647 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5648 goto error;
5649 }
5650
5651 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5652 1, curpx, &rs->rules, file, line, &errmsg);
5653 if (!chk) {
5654 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5655 goto error;
5656 }
5657 chk->index = 0;
5658 LIST_ADDQ(&rs->rules, &chk->list);
5659
5660 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
5661 "min-recv", "4",
5662 "error-status", "L7RSP",
5663 "on-error", "%[check.payload(),cut_crlf]",
5664 ""},
5665 1, curpx, &rs->rules, file, line, &errmsg);
5666 if (!chk) {
5667 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5668 goto error;
5669 }
5670 chk->index = 1;
5671 LIST_ADDQ(&rs->rules, &chk->list);
5672
5673 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
5674 "min-recv", "4",
5675 "error-status", "L7STS",
5676 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5677 "status-code", "check.payload(0,3)",
5678 ""},
5679 1, curpx, &rs->rules, file, line, &errmsg);
5680 if (!chk) {
5681 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5682 goto error;
5683 }
5684 chk->index = 2;
5685 LIST_ADDQ(&rs->rules, &chk->list);
5686
5687 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
5688 1, curpx, &rs->rules, file, line, &errmsg);
5689 if (!chk) {
5690 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5691 goto error;
5692 }
5693 chk->index = 3;
5694 LIST_ADDQ(&rs->rules, &chk->list);
5695
5696 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
5697 "min-recv", "4",
5698 "error-status", "L7STS",
5699 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5700 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5701 "status-code", "check.payload(0,3)",
5702 ""},
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 = 4;
5709 LIST_ADDQ(&rs->rules, &chk->list);
5710
5711 LIST_ADDQ(&tcpchecks_list, &rs->list);
5712
5713 ruleset_found:
5714 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005715 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005716
5717 out:
5718 free(errmsg);
5719 return err_code;
5720
5721 error:
5722 free(cmd);
5723 free(var);
5724 free_tcpcheck_vars(&rules->preset_vars);
5725 tcpcheck_ruleset_release(rs);
5726 err_code |= ERR_ALERT | ERR_FATAL;
5727 goto out;
5728}
Christopher Faulet811f78c2020-04-01 11:10:27 +02005729
Christopher Fauletce355072020-04-02 11:44:39 +02005730/* Parses the "option pgsql-check" proxy keyword */
5731int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5732 const char *file, int line)
5733{
5734 static char pgsql_req[] = {
5735 "%[var(check.plen),htonl,hex]" /* The packet length*/
5736 "00030000" /* the version 3.0 */
5737 "7573657200" /* "user" key */
5738 "%[var(check.username),hex]00" /* the username */
5739 "00"
5740 };
5741
5742 struct tcpcheck_ruleset *rs = NULL;
5743 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5744 struct tcpcheck_rule *chk;
5745 struct tcpcheck_var *var = NULL;
5746 char *user = NULL, *errmsg = NULL;
5747 size_t packetlen = 0;
5748 int err_code = 0;
5749
5750 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5751 err_code |= ERR_WARN;
5752
5753 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5754 goto out;
5755
Christopher Fauletce355072020-04-02 11:44:39 +02005756 curpx->options2 &= ~PR_O2_CHK_ANY;
5757 curpx->options2 |= PR_O2_TCPCHK_CHK;
5758
5759 free_tcpcheck_vars(&rules->preset_vars);
5760 rules->list = NULL;
5761 rules->flags = 0;
5762
5763 cur_arg += 2;
5764 if (!*args[cur_arg] || !*args[cur_arg+1]) {
5765 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
5766 file, line, args[0], args[1]);
5767 goto error;
5768 }
5769 if (strcmp(args[cur_arg], "user") == 0) {
5770 packetlen = 15 + strlen(args[cur_arg+1]);
5771 user = strdup(args[cur_arg+1]);
5772
5773 var = tcpcheck_var_create("check.username");
5774 if (user == NULL || var == NULL) {
5775 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5776 goto error;
5777 }
5778 var->data.type = SMP_T_STR;
5779 var->data.u.str.area = user;
5780 var->data.u.str.data = strlen(user);
5781 LIST_INIT(&var->list);
5782 LIST_ADDQ(&rules->preset_vars, &var->list);
5783 user = NULL;
5784 var = NULL;
5785
5786 var = tcpcheck_var_create("check.plen");
5787 if (var == NULL) {
5788 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5789 goto error;
5790 }
5791 var->data.type = SMP_T_SINT;
5792 var->data.u.sint = packetlen;
5793 LIST_INIT(&var->list);
5794 LIST_ADDQ(&rules->preset_vars, &var->list);
5795 var = NULL;
5796 }
5797 else {
5798 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
5799 file, line, args[0], args[1]);
5800 goto error;
5801 }
5802
5803 rs = tcpcheck_ruleset_lookup("*pgsql-check");
5804 if (rs)
5805 goto ruleset_found;
5806
5807 rs = tcpcheck_ruleset_create("*pgsql-check");
5808 if (rs == NULL) {
5809 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5810 goto error;
5811 }
5812
5813 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5814 1, curpx, &rs->rules, file, line, &errmsg);
5815 if (!chk) {
5816 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5817 goto error;
5818 }
5819 chk->index = 0;
5820 LIST_ADDQ(&rs->rules, &chk->list);
5821
5822 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
5823 1, curpx, &rs->rules, file, line, &errmsg);
5824 if (!chk) {
5825 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5826 goto error;
5827 }
5828 chk->index = 1;
5829 LIST_ADDQ(&rs->rules, &chk->list);
5830
5831 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
5832 "min-recv", "5",
5833 "error-status", "L7RSP",
5834 "on-error", "%[check.payload(6,0)]",
5835 ""},
5836 1, curpx, &rs->rules, file, line, &errmsg);
5837 if (!chk) {
5838 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5839 goto error;
5840 }
5841 chk->index = 2;
5842 LIST_ADDQ(&rs->rules, &chk->list);
5843
5844 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
5845 "min-recv", "9",
5846 "error-status", "L7STS",
5847 "on-success", "PostgreSQL server is ok",
5848 "on-error", "PostgreSQL unknown error",
5849 ""},
5850 1, curpx, &rs->rules, file, line, &errmsg);
5851 if (!chk) {
5852 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5853 goto error;
5854 }
5855 chk->index = 3;
5856 LIST_ADDQ(&rs->rules, &chk->list);
5857
5858 LIST_ADDQ(&tcpchecks_list, &rs->list);
5859
5860 ruleset_found:
5861 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005862 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02005863
5864 out:
5865 free(errmsg);
5866 return err_code;
5867
5868 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02005869 free(user);
5870 free(var);
5871 free_tcpcheck_vars(&rules->preset_vars);
5872 tcpcheck_ruleset_release(rs);
5873 err_code |= ERR_ALERT | ERR_FATAL;
5874 goto out;
5875}
5876
5877
5878/* Parses the "option mysql-check" proxy keyword */
5879int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5880 const char *file, int line)
5881{
5882 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
5883 * const char mysql40_client_auth_pkt[] = {
5884 * "\x0e\x00\x00" // packet length
5885 * "\x01" // packet number
5886 * "\x00\x00" // client capabilities
5887 * "\x00\x00\x01" // max packet
5888 * "haproxy\x00" // username (null terminated string)
5889 * "\x00" // filler (always 0x00)
5890 * "\x01\x00\x00" // packet length
5891 * "\x00" // packet number
5892 * "\x01" // COM_QUIT command
5893 * };
5894 */
5895 static char mysql40_rsname[] = "*mysql40-check";
5896 static char mysql40_req[] = {
5897 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5898 "0080" /* client capabilities */
5899 "000001" /* max packet */
5900 "%[var(check.username),hex]00" /* the username */
5901 "00" /* filler (always 0x00) */
5902 "010000" /* packet length*/
5903 "00" /* sequence ID */
5904 "01" /* COM_QUIT command */
5905 };
5906
5907 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
5908 * const char mysql41_client_auth_pkt[] = {
5909 * "\x0e\x00\x00\" // packet length
5910 * "\x01" // packet number
5911 * "\x00\x00\x00\x00" // client capabilities
5912 * "\x00\x00\x00\x01" // max packet
5913 * "\x21" // character set (UTF-8)
5914 * char[23] // All zeroes
5915 * "haproxy\x00" // username (null terminated string)
5916 * "\x00" // filler (always 0x00)
5917 * "\x01\x00\x00" // packet length
5918 * "\x00" // packet number
5919 * "\x01" // COM_QUIT command
5920 * };
5921 */
5922 static char mysql41_rsname[] = "*mysql41-check";
5923 static char mysql41_req[] = {
5924 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5925 "00820000" /* client capabilities */
5926 "00800001" /* max packet */
5927 "21" /* character set (UTF-8) */
5928 "000000000000000000000000" /* 23 bytes, al zeroes */
5929 "0000000000000000000000"
5930 "%[var(check.username),hex]00" /* the username */
5931 "00" /* filler (always 0x00) */
5932 "010000" /* packet length*/
5933 "00" /* sequence ID */
5934 "01" /* COM_QUIT command */
5935 };
5936
5937 struct tcpcheck_ruleset *rs = NULL;
5938 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5939 struct tcpcheck_rule *chk;
5940 struct tcpcheck_var *var = NULL;
5941 char *mysql_rsname = "*mysql-check";
5942 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
5943 int index = 0, err_code = 0;
5944
5945 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5946 err_code |= ERR_WARN;
5947
5948 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5949 goto out;
5950
Christopher Fauletf2b3be52020-04-02 18:07:37 +02005951 curpx->options2 &= ~PR_O2_CHK_ANY;
5952 curpx->options2 |= PR_O2_TCPCHK_CHK;
5953
5954 free_tcpcheck_vars(&rules->preset_vars);
5955 rules->list = NULL;
5956 rules->flags = 0;
5957
5958 cur_arg += 2;
5959 if (*args[cur_arg]) {
5960 char *user;
5961 int packetlen, userlen;
5962
5963 if (strcmp(args[cur_arg], "user") != 0) {
5964 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
5965 file, line, args[0], args[1], args[cur_arg]);
5966 goto error;
5967 }
5968
5969 if (*(args[cur_arg+1]) == 0) {
5970 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
5971 file, line, args[0], args[1], args[cur_arg]);
5972 goto error;
5973 }
5974
5975 hdr = calloc(4, sizeof(*hdr));
5976 user = strdup(args[cur_arg+1]);
5977 userlen = strlen(args[cur_arg+1]);
5978
5979 if (hdr == NULL || user == NULL) {
5980 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5981 goto error;
5982 }
5983
5984 if (*args[cur_arg+2]) {
5985 if (strcmp(args[cur_arg+2], "post-41") != 0) {
5986 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
5987 file, line, args[cur_arg], args[cur_arg+2]);
5988 goto error;
5989 }
5990 packetlen = userlen + 7 + 27;
5991 mysql_req = mysql41_req;
5992 mysql_rsname = mysql41_rsname;
5993 }
5994 else {
5995 packetlen = userlen + 7;
5996 mysql_req = mysql40_req;
5997 mysql_rsname = mysql40_rsname;
5998 }
5999
6000 hdr[0] = (unsigned char)(packetlen & 0xff);
6001 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6002 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6003 hdr[3] = 1;
6004
6005 var = tcpcheck_var_create("check.header");
6006 if (var == NULL) {
6007 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6008 goto error;
6009 }
6010 var->data.type = SMP_T_STR;
6011 var->data.u.str.area = hdr;
6012 var->data.u.str.data = 4;
6013 LIST_INIT(&var->list);
6014 LIST_ADDQ(&rules->preset_vars, &var->list);
6015 hdr = NULL;
6016 var = NULL;
6017
6018 var = tcpcheck_var_create("check.username");
6019 if (var == NULL) {
6020 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6021 goto error;
6022 }
6023 var->data.type = SMP_T_STR;
6024 var->data.u.str.area = user;
6025 var->data.u.str.data = strlen(user);
6026 LIST_INIT(&var->list);
6027 LIST_ADDQ(&rules->preset_vars, &var->list);
6028 user = NULL;
6029 var = NULL;
6030 }
6031
6032 rs = tcpcheck_ruleset_lookup(mysql_rsname);
6033 if (rs)
6034 goto ruleset_found;
6035
6036 rs = tcpcheck_ruleset_create(mysql_rsname);
6037 if (rs == NULL) {
6038 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6039 goto error;
6040 }
6041
6042 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6043 1, curpx, &rs->rules, file, line, &errmsg);
6044 if (!chk) {
6045 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6046 goto error;
6047 }
6048 chk->index = index++;
6049 LIST_ADDQ(&rs->rules, &chk->list);
6050
6051 if (mysql_req) {
6052 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6053 1, curpx, &rs->rules, file, line, &errmsg);
6054 if (!chk) {
6055 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6056 goto error;
6057 }
6058 chk->index = index++;
6059 LIST_ADDQ(&rs->rules, &chk->list);
6060 }
6061
6062 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6063 1, curpx, &rs->rules, file, line, &errmsg);
6064 if (!chk) {
6065 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6066 goto error;
6067 }
6068 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6069 chk->index = index++;
6070 LIST_ADDQ(&rs->rules, &chk->list);
6071
6072 if (mysql_req) {
6073 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6074 1, curpx, &rs->rules, file, line, &errmsg);
6075 if (!chk) {
6076 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6077 goto error;
6078 }
6079 chk->expect.custom = tcpcheck_mysql_expect_ok;
6080 chk->index = index++;
6081 LIST_ADDQ(&rs->rules, &chk->list);
6082 }
6083
6084 LIST_ADDQ(&tcpchecks_list, &rs->list);
6085
6086 ruleset_found:
6087 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006088 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006089
6090 out:
6091 free(errmsg);
6092 return err_code;
6093
6094 error:
6095 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006096 free(user);
6097 free(var);
6098 free_tcpcheck_vars(&rules->preset_vars);
6099 tcpcheck_ruleset_release(rs);
6100 err_code |= ERR_ALERT | ERR_FATAL;
6101 goto out;
6102}
6103
Christopher Faulet1997eca2020-04-03 23:13:50 +02006104int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6105 const char *file, int line)
6106{
6107 static char *ldap_req = "300C020101600702010304008000";
6108
6109 struct tcpcheck_ruleset *rs = NULL;
6110 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6111 struct tcpcheck_rule *chk;
6112 char *errmsg = NULL;
6113 int err_code = 0;
6114
6115 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6116 err_code |= ERR_WARN;
6117
6118 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6119 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006120
6121 curpx->options2 &= ~PR_O2_CHK_ANY;
6122 curpx->options2 |= PR_O2_TCPCHK_CHK;
6123
6124 free_tcpcheck_vars(&rules->preset_vars);
6125 rules->list = NULL;
6126 rules->flags = 0;
6127
6128 rs = tcpcheck_ruleset_lookup("*ldap-check");
6129 if (rs)
6130 goto ruleset_found;
6131
6132 rs = tcpcheck_ruleset_create("*ldap-check");
6133 if (rs == NULL) {
6134 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6135 goto error;
6136 }
6137
6138 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6139 1, curpx, &rs->rules, file, line, &errmsg);
6140 if (!chk) {
6141 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6142 goto error;
6143 }
6144 chk->index = 0;
6145 LIST_ADDQ(&rs->rules, &chk->list);
6146
6147 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6148 "min-recv", "14",
6149 "on-error", "Not LDAPv3 protocol",
6150 ""},
6151 1, curpx, &rs->rules, file, line, &errmsg);
6152 if (!chk) {
6153 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6154 goto error;
6155 }
6156 chk->index = 1;
6157 LIST_ADDQ(&rs->rules, &chk->list);
6158
6159 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6160 1, curpx, &rs->rules, file, line, &errmsg);
6161 if (!chk) {
6162 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6163 goto error;
6164 }
6165 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6166 chk->index = 2;
6167 LIST_ADDQ(&rs->rules, &chk->list);
6168
6169 LIST_ADDQ(&tcpchecks_list, &rs->list);
6170
6171 ruleset_found:
6172 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006173 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006174
6175 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006176 free(errmsg);
6177 return err_code;
6178
6179 error:
6180 tcpcheck_ruleset_release(rs);
6181 err_code |= ERR_ALERT | ERR_FATAL;
6182 goto out;
6183}
6184
6185int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6186 const char *file, int line)
6187{
6188 struct tcpcheck_ruleset *rs = NULL;
6189 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6190 struct tcpcheck_rule *chk;
6191 char *spop_req = NULL;
6192 char *errmsg = NULL;
6193 int spop_len = 0, err_code = 0;
6194
6195 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6196 err_code |= ERR_WARN;
6197
6198 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6199 goto out;
6200
Christopher Faulet267b01b2020-04-04 10:27:09 +02006201 curpx->options2 &= ~PR_O2_CHK_ANY;
6202 curpx->options2 |= PR_O2_TCPCHK_CHK;
6203
6204 free_tcpcheck_vars(&rules->preset_vars);
6205 rules->list = NULL;
6206 rules->flags = 0;
6207
6208
6209 rs = tcpcheck_ruleset_lookup("*spop-check");
6210 if (rs)
6211 goto ruleset_found;
6212
6213 rs = tcpcheck_ruleset_create("*spop-check");
6214 if (rs == NULL) {
6215 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6216 goto error;
6217 }
6218
6219 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6220 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6221 goto error;
6222 }
6223 chunk_reset(&trash);
6224 dump_binary(&trash, spop_req, spop_len);
6225 trash.area[trash.data] = '\0';
6226
6227 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6228 1, curpx, &rs->rules, file, line, &errmsg);
6229 if (!chk) {
6230 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6231 goto error;
6232 }
6233 chk->index = 0;
6234 LIST_ADDQ(&rs->rules, &chk->list);
6235
6236 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
6237 1, curpx, &rs->rules, file, line, &errmsg);
6238 if (!chk) {
6239 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6240 goto error;
6241 }
6242 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6243 chk->index = 1;
6244 LIST_ADDQ(&rs->rules, &chk->list);
6245
6246 LIST_ADDQ(&tcpchecks_list, &rs->list);
6247
6248 ruleset_found:
6249 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006250 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006251
6252 out:
6253 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006254 free(errmsg);
6255 return err_code;
6256
6257 error:
6258 tcpcheck_ruleset_release(rs);
6259 err_code |= ERR_ALERT | ERR_FATAL;
6260 goto out;
6261}
Christopher Fauletce355072020-04-02 11:44:39 +02006262
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006263int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6264 const char *file, int line)
6265{
6266 static const char *http_req = "OPTIONS / HTTP/1.0\r\n";
6267 int err_code = 0;
6268
6269 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6270 err_code |= ERR_WARN;
6271
6272 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6273 goto out;
6274
6275 /* use HTTP request to check servers' health */
6276 free(curpx->check_req);
6277 free(curpx->check_hdrs);
6278 free(curpx->check_body);
6279
6280 curpx->check_req = curpx->check_hdrs = curpx->check_body = NULL;
6281 curpx->check_len = curpx->check_hdrs_len = curpx->check_body_len = 0;
6282 curpx->options2 &= ~PR_O2_CHK_ANY;
6283 curpx->options2 |= PR_O2_HTTP_CHK;
6284
6285 cur_arg += 2;
6286 if (!*args[cur_arg]) { /* no argument */
6287 curpx->check_req = strdup(http_req); /* default request */
6288 curpx->check_len = strlen(http_req);
6289 }
6290 else if (!*args[cur_arg+1]) { /* one argument : URI */
6291 curpx->check_len = strlen(args[cur_arg]) + strlen("OPTIONS HTTP/1.0\r\n");
6292 curpx->check_req = malloc(curpx->check_len+1);
6293 curpx->check_len = snprintf(curpx->check_req, curpx->check_len+1,
6294 "OPTIONS %s HTTP/1.0\r\n", args[cur_arg]);
6295 }
6296 else if (!*args[cur_arg+2]) { /* two arguments : METHOD URI */
6297 curpx->check_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + strlen(" HTTP/1.0\r\n") + 1;
6298 curpx->check_req = malloc(curpx->check_len+1);
6299 curpx->check_len = snprintf(curpx->check_req, curpx->check_len+1,
6300 "%s %s HTTP/1.0\r\n", args[cur_arg], args[cur_arg+1]);
6301 }
6302 else { /* 3 arguments : METHOD URI HTTP_VER */
6303 char *hdrs = strstr(args[cur_arg+2], "\r\n");
6304 char *body = strstr(args[cur_arg+2], "\r\n\r\n");
6305
6306 if (hdrs || body) {
6307 ha_warning("parsing [%s:%d]: '%s %s' : hiding headers or body at the end of the version string is deprecated."
6308 " Please, consider to use 'http-check send' directive instead.\n",
6309 file, line, args[0], args[1]);
6310 err_code |= ERR_WARN;
6311 }
6312
6313 if (hdrs == body)
6314 hdrs = NULL;
6315 if (hdrs) {
6316 *hdrs = '\0';
6317 hdrs += 2;
6318 }
6319 if (body) {
6320 *body = '\0';
6321 body += 4;
6322 }
6323
6324 curpx->check_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + strlen(args[cur_arg+2]) + 4;
6325 curpx->check_req = malloc(curpx->check_len+1);
6326 snprintf(curpx->check_req, curpx->check_len+1, "%s %s %s\r\n",
6327 args[cur_arg], args[cur_arg+1], args[cur_arg+2]);
6328 if (hdrs) {
6329 curpx->check_hdrs_len = strlen(hdrs) + 2;
6330 curpx->check_hdrs = malloc(curpx->check_hdrs_len+1);
6331 snprintf(curpx->check_hdrs, curpx->check_hdrs_len+1, "%s\r\n", hdrs);
6332 }
6333 if (body) {
6334 curpx->check_body_len = strlen(body);
6335 curpx->check_body = strdup(body);
6336 }
6337 }
6338 out:
6339 return err_code;
6340
6341 error:
6342 err_code |= ERR_ALERT | ERR_FATAL;
6343 goto out;
6344}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006345
Christopher Faulet6f557912020-04-09 15:58:50 +02006346int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6347 const char *file, int line)
6348{
6349 int err_code = 0;
6350
6351 free(curpx->check_req);
6352 curpx->check_req = NULL;
6353 curpx->options2 &= ~PR_O2_CHK_ANY;
6354 curpx->options2 |= PR_O2_EXT_CHK;
6355 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6356 goto out;
6357
6358 out:
6359 return err_code;
6360}
6361
Christopher Fauletce8111e2020-04-06 15:04:11 +02006362/* Parse the "addr" server keyword */
6363static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6364 char **errmsg)
6365{
6366 struct sockaddr_storage *sk;
6367 struct protocol *proto;
6368 int port1, port2, err_code = 0;
6369
6370
6371 if (!*args[*cur_arg+1]) {
6372 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6373 goto error;
6374 }
6375
6376 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6377 if (!sk) {
6378 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6379 goto error;
6380 }
6381
6382 proto = protocol_by_family(sk->ss_family);
6383 if (!proto || !proto->connect) {
6384 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6385 args[*cur_arg], args[*cur_arg+1]);
6386 goto error;
6387 }
6388
6389 if (port1 != port2) {
6390 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6391 args[*cur_arg], args[*cur_arg+1]);
6392 goto error;
6393 }
6394
6395 srv->check.addr = srv->agent.addr = *sk;
6396 srv->flags |= SRV_F_CHECKADDR;
6397 srv->flags |= SRV_F_AGENTADDR;
6398
6399 out:
6400 return err_code;
6401
6402 error:
6403 err_code |= ERR_ALERT | ERR_FATAL;
6404 goto out;
6405}
6406
6407
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006408/* Parse the "agent-addr" server keyword */
6409static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6410 char **errmsg)
6411{
6412 int err_code = 0;
6413
6414 if (!*(args[*cur_arg+1])) {
6415 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6416 goto error;
6417 }
6418 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6419 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6420 goto error;
6421 }
6422
6423 out:
6424 return err_code;
6425
6426 error:
6427 err_code |= ERR_ALERT | ERR_FATAL;
6428 goto out;
6429}
6430
6431/* Parse the "agent-check" server keyword */
6432static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6433 char **errmsg)
6434{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006435 struct tcpcheck_ruleset *rs = NULL;
6436 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6437 struct tcpcheck_rule *chk;
6438 int err_code = 0;
6439
6440 if (srv->do_agent)
6441 goto out;
6442
6443 if (!rules) {
6444 rules = calloc(1, sizeof(*rules));
6445 if (!rules) {
6446 memprintf(errmsg, "out of memory.");
6447 goto error;
6448 }
6449 LIST_INIT(&rules->preset_vars);
6450 srv->agent.tcpcheck_rules = rules;
6451 }
6452 rules->list = NULL;
6453 rules->flags = 0;
6454
6455 rs = tcpcheck_ruleset_lookup("*agent-check");
6456 if (rs)
6457 goto ruleset_found;
6458
6459 rs = tcpcheck_ruleset_create("*agent-check");
6460 if (rs == NULL) {
6461 memprintf(errmsg, "out of memory.");
6462 goto error;
6463 }
6464
6465 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6466 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6467 if (!chk) {
6468 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6469 goto error;
6470 }
6471 chk->index = 0;
6472 LIST_ADDQ(&rs->rules, &chk->list);
6473
6474 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6475 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6476 if (!chk) {
6477 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6478 goto error;
6479 }
6480 chk->expect.custom = tcpcheck_agent_expect_reply;
6481 chk->index = 1;
6482 LIST_ADDQ(&rs->rules, &chk->list);
6483
6484 LIST_ADDQ(&tcpchecks_list, &rs->list);
6485
6486 ruleset_found:
6487 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006488 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006489 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006490
6491 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006492 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006493
6494 error:
6495 deinit_srv_agent_check(srv);
6496 tcpcheck_ruleset_release(rs);
6497 err_code |= ERR_ALERT | ERR_FATAL;
6498 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006499}
6500
6501/* Parse the "agent-inter" server keyword */
6502static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6503 char **errmsg)
6504{
6505 const char *err = NULL;
6506 unsigned int delay;
6507 int err_code = 0;
6508
6509 if (!*(args[*cur_arg+1])) {
6510 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6511 goto error;
6512 }
6513
6514 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6515 if (err == PARSE_TIME_OVER) {
6516 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6517 args[*cur_arg+1], args[*cur_arg], srv->id);
6518 goto error;
6519 }
6520 else if (err == PARSE_TIME_UNDER) {
6521 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6522 args[*cur_arg+1], args[*cur_arg], srv->id);
6523 goto error;
6524 }
6525 else if (err) {
6526 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6527 *err, srv->id);
6528 goto error;
6529 }
6530 if (delay <= 0) {
6531 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6532 delay, args[*cur_arg], srv->id);
6533 goto error;
6534 }
6535 srv->agent.inter = delay;
6536
6537 out:
6538 return err_code;
6539
6540 error:
6541 err_code |= ERR_ALERT | ERR_FATAL;
6542 goto out;
6543}
6544
6545/* Parse the "agent-port" server keyword */
6546static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6547 char **errmsg)
6548{
6549 int err_code = 0;
6550
6551 if (!*(args[*cur_arg+1])) {
6552 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6553 goto error;
6554 }
6555
6556 global.maxsock++;
6557 srv->agent.port = atol(args[*cur_arg+1]);
6558
6559 out:
6560 return err_code;
6561
6562 error:
6563 err_code |= ERR_ALERT | ERR_FATAL;
6564 goto out;
6565}
6566
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006567int set_srv_agent_send(struct server *srv, const char *send)
6568{
6569 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6570 struct tcpcheck_var *var = NULL;
6571 char *str;
6572
6573 str = strdup(send);
6574 var = tcpcheck_var_create("check.agent_string");
6575 if (str == NULL || var == NULL)
6576 goto error;
6577
6578 free_tcpcheck_vars(&rules->preset_vars);
6579
6580 var->data.type = SMP_T_STR;
6581 var->data.u.str.area = str;
6582 var->data.u.str.data = strlen(str);
6583 LIST_INIT(&var->list);
6584 LIST_ADDQ(&rules->preset_vars, &var->list);
6585
6586 return 1;
6587
6588 error:
6589 free(str);
6590 free(var);
6591 return 0;
6592}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006593
6594/* Parse the "agent-send" server keyword */
6595static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6596 char **errmsg)
6597{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006598 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006599 int err_code = 0;
6600
6601 if (!*(args[*cur_arg+1])) {
6602 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
6603 goto error;
6604 }
6605
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006606 if (!rules) {
6607 rules = calloc(1, sizeof(*rules));
6608 if (!rules) {
6609 memprintf(errmsg, "out of memory.");
6610 goto error;
6611 }
6612 LIST_INIT(&rules->preset_vars);
6613 srv->agent.tcpcheck_rules = rules;
6614 }
6615
6616 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006617 memprintf(errmsg, "out of memory.");
6618 goto error;
6619 }
6620
6621 out:
6622 return err_code;
6623
6624 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006625 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006626 err_code |= ERR_ALERT | ERR_FATAL;
6627 goto out;
6628}
6629
6630/* Parse the "no-agent-send" server keyword */
6631static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6632 char **errmsg)
6633{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006634 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006635 return 0;
6636}
6637
Christopher Fauletce8111e2020-04-06 15:04:11 +02006638/* Parse the "check" server keyword */
6639static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6640 char **errmsg)
6641{
6642 srv->do_check = 1;
6643 return 0;
6644}
6645
6646/* Parse the "check-send-proxy" server keyword */
6647static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6648 char **errmsg)
6649{
6650 srv->check.send_proxy = 1;
6651 return 0;
6652}
6653
6654/* Parse the "check-via-socks4" server keyword */
6655static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6656 char **errmsg)
6657{
6658 srv->check.via_socks4 = 1;
6659 return 0;
6660}
6661
6662/* Parse the "no-check" server keyword */
6663static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6664 char **errmsg)
6665{
6666 deinit_srv_check(srv);
6667 return 0;
6668}
6669
6670/* Parse the "no-check-send-proxy" server keyword */
6671static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6672 char **errmsg)
6673{
6674 srv->check.send_proxy = 0;
6675 return 0;
6676}
6677
6678/* Parse the "rise" server keyword */
6679static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6680 char **errmsg)
6681{
6682 int err_code = 0;
6683
6684 if (!*args[*cur_arg + 1]) {
6685 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6686 goto error;
6687 }
6688
6689 srv->check.rise = atol(args[*cur_arg+1]);
6690 if (srv->check.rise <= 0) {
6691 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6692 goto error;
6693 }
6694
6695 if (srv->check.health)
6696 srv->check.health = srv->check.rise;
6697
6698 out:
6699 return err_code;
6700
6701 error:
6702 deinit_srv_agent_check(srv);
6703 err_code |= ERR_ALERT | ERR_FATAL;
6704 goto out;
6705 return 0;
6706}
6707
6708/* Parse the "fall" server keyword */
6709static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6710 char **errmsg)
6711{
6712 int err_code = 0;
6713
6714 if (!*args[*cur_arg + 1]) {
6715 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6716 goto error;
6717 }
6718
6719 srv->check.fall = atol(args[*cur_arg+1]);
6720 if (srv->check.fall <= 0) {
6721 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6722 goto error;
6723 }
6724
6725 out:
6726 return err_code;
6727
6728 error:
6729 deinit_srv_agent_check(srv);
6730 err_code |= ERR_ALERT | ERR_FATAL;
6731 goto out;
6732 return 0;
6733}
6734
6735/* Parse the "inter" server keyword */
6736static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6737 char **errmsg)
6738{
6739 const char *err = NULL;
6740 unsigned int delay;
6741 int err_code = 0;
6742
6743 if (!*(args[*cur_arg+1])) {
6744 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6745 goto error;
6746 }
6747
6748 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6749 if (err == PARSE_TIME_OVER) {
6750 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6751 args[*cur_arg+1], args[*cur_arg], srv->id);
6752 goto error;
6753 }
6754 else if (err == PARSE_TIME_UNDER) {
6755 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6756 args[*cur_arg+1], args[*cur_arg], srv->id);
6757 goto error;
6758 }
6759 else if (err) {
6760 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6761 *err, srv->id);
6762 goto error;
6763 }
6764 if (delay <= 0) {
6765 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6766 delay, args[*cur_arg], srv->id);
6767 goto error;
6768 }
6769 srv->check.inter = delay;
6770
6771 out:
6772 return err_code;
6773
6774 error:
6775 err_code |= ERR_ALERT | ERR_FATAL;
6776 goto out;
6777}
6778
6779
6780/* Parse the "fastinter" server keyword */
6781static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6782 char **errmsg)
6783{
6784 const char *err = NULL;
6785 unsigned int delay;
6786 int err_code = 0;
6787
6788 if (!*(args[*cur_arg+1])) {
6789 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6790 goto error;
6791 }
6792
6793 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6794 if (err == PARSE_TIME_OVER) {
6795 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6796 args[*cur_arg+1], args[*cur_arg], srv->id);
6797 goto error;
6798 }
6799 else if (err == PARSE_TIME_UNDER) {
6800 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6801 args[*cur_arg+1], args[*cur_arg], srv->id);
6802 goto error;
6803 }
6804 else if (err) {
6805 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6806 *err, srv->id);
6807 goto error;
6808 }
6809 if (delay <= 0) {
6810 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6811 delay, args[*cur_arg], srv->id);
6812 goto error;
6813 }
6814 srv->check.fastinter = delay;
6815
6816 out:
6817 return err_code;
6818
6819 error:
6820 err_code |= ERR_ALERT | ERR_FATAL;
6821 goto out;
6822}
6823
6824
6825/* Parse the "downinter" server keyword */
6826static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6827 char **errmsg)
6828{
6829 const char *err = NULL;
6830 unsigned int delay;
6831 int err_code = 0;
6832
6833 if (!*(args[*cur_arg+1])) {
6834 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6835 goto error;
6836 }
6837
6838 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6839 if (err == PARSE_TIME_OVER) {
6840 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6841 args[*cur_arg+1], args[*cur_arg], srv->id);
6842 goto error;
6843 }
6844 else if (err == PARSE_TIME_UNDER) {
6845 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6846 args[*cur_arg+1], args[*cur_arg], srv->id);
6847 goto error;
6848 }
6849 else if (err) {
6850 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6851 *err, srv->id);
6852 goto error;
6853 }
6854 if (delay <= 0) {
6855 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6856 delay, args[*cur_arg], srv->id);
6857 goto error;
6858 }
6859 srv->check.downinter = delay;
6860
6861 out:
6862 return err_code;
6863
6864 error:
6865 err_code |= ERR_ALERT | ERR_FATAL;
6866 goto out;
6867}
6868
6869/* Parse the "port" server keyword */
6870static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6871 char **errmsg)
6872{
6873 int err_code = 0;
6874
6875 if (!*(args[*cur_arg+1])) {
6876 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6877 goto error;
6878 }
6879
6880 global.maxsock++;
6881 srv->check.port = atol(args[*cur_arg+1]);
6882 srv->flags |= SRV_F_CHECKPORT;
6883
6884 out:
6885 return err_code;
6886
6887 error:
6888 err_code |= ERR_ALERT | ERR_FATAL;
6889 goto out;
6890}
6891
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006892static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02006893 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
6894 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
6895 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006896 { 0, NULL, NULL },
6897}};
6898
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006899static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02006900 { "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 +02006901 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
6902 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
6903 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
6904 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
6905 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006906 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
6907 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
6908 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006909 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006910 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
6911 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
6912 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
6913 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
6914 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
6915 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
6916 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
6917 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006918 { NULL, NULL, 0 },
6919}};
6920
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006921INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006922INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006923
Willy Tarreaubd741542010-03-16 18:46:54 +01006924/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02006925 * Local variables:
6926 * c-indent-level: 8
6927 * c-basic-offset: 8
6928 * End:
6929 */