blob: e43e73224ad1a62805a648158cb8a3988fbc5838 [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 */
Christopher Faulet12d57402020-04-10 09:58:42 +02002343 if (rule->expect.flags & TCPCHK_EXPT_FL_CAP) {
Christopher Faulet206368d2020-04-03 14:51:06 +02002344 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: ");
Christopher Faulet12d57402020-04-10 09:58:42 +02002363 if (rule->expect.flags & TCPCHK_EXPT_FL_CAP) {
Christopher Faulet206368d2020-04-03 14:51:06 +02002364 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 Faulet12d57402020-04-10 09:58:42 +02003110 int match, inverse;
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 Faulet12d57402020-04-10 09:58:42 +02003127 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003128 /* Make GCC happy ; initialize match to a failure state. */
Christopher Faulet12d57402020-04-10 09:58:42 +02003129 match = inverse;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003130
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003131 switch (expect->type) {
3132 case TCPCHK_EXPECT_STRING:
3133 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003134 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 +02003135 break;
3136 case TCPCHK_EXPECT_REGEX:
Christopher Faulet12d57402020-04-10 09:58:42 +02003137 if (expect->flags & TCPCHK_EXPT_FL_CAP)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003138 match = regex_exec_match2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1),
3139 MAX_MATCH, pmatch, 0);
3140 else
3141 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
3142 break;
Willy Tarreau7df8ca62019-07-15 10:57:51 +02003143
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003144 case TCPCHK_EXPECT_REGEX_BINARY:
3145 chunk_reset(&trash);
3146 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet12d57402020-04-10 09:58:42 +02003147 if (expect->flags & TCPCHK_EXPT_FL_CAP)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003148 match = regex_exec_match2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1),
3149 MAX_MATCH, pmatch, 0);
3150 else
3151 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
3152 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003153 case TCPCHK_EXPECT_CUSTOM:
3154 if (expect->custom)
3155 ret = expect->custom(check, rule, last_read);
3156 goto out;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003157 case TCPCHK_EXPECT_UNDEF:
3158 /* Should never happen. */
3159 ret = TCPCHK_EVAL_STOP;
3160 goto out;
3161 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003162
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003163
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003164 /* Wait for more data on mismatch only if no minimum is defined (-1),
3165 * otherwise the absence of match is already conclusive.
3166 */
3167 if (!match && !last_read && (expect->min_recv == -1)) {
3168 ret = TCPCHK_EVAL_WAIT;
3169 goto out;
3170 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003171
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003172 /* Result as expected, next rule. */
Christopher Faulet12d57402020-04-10 09:58:42 +02003173 if (match ^ inverse)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003174 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003175
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01003176
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003177 /* From this point on, we matched something we did not want, this is an error state. */
3178 ret = TCPCHK_EVAL_STOP;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003179 msg = alloc_trash_chunk();
Christopher Faulet206368d2020-04-03 14:51:06 +02003180 if (msg)
3181 tcpcheck_onerror_message(msg, check, rule, match, ist(NULL));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003182 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
Christopher Faulet206368d2020-04-03 14:51:06 +02003183 free_trash_chunk(msg);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003184 ret = TCPCHK_EVAL_STOP;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003185
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003186 out:
3187 return ret;
3188}
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003189
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003190/* Evaluate a TCPCHK_ACT_ACTION_KW rule. It returns 1 to evaluate the next rule, 0
3191 * to wait and -1 to stop the check.
3192 */
3193static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
3194{
3195 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3196 struct act_rule *act_rule;
3197 enum act_return act_ret;
3198
3199 act_rule =rule->action_kw.rule;
3200 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
3201 if (act_ret != ACT_RET_CONT) {
3202 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
3203 tcpcheck_get_step_id(check, rule));
3204 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3205 ret = TCPCHK_EVAL_STOP;
3206 }
3207
3208 return ret;
3209}
3210
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003211/* proceed with next steps for the TCP checks <check>. Note that this is called
3212 * both from the connection's wake() callback and from the check scheduling task.
3213 * It returns 0 on normal cases, or <0 if a close() has happened on an existing
3214 * connection, presenting the risk of an fd replacement.
3215 *
3216 * Please do NOT place any return statement in this function and only leave
3217 * via the out_end_tcpcheck label after setting retcode.
3218 */
3219static int tcpcheck_main(struct check *check)
3220{
3221 struct tcpcheck_rule *rule;
3222 struct conn_stream *cs = check->cs;
3223 struct connection *conn = cs_conn(cs);
3224 int must_read = 1, last_read = 0;
3225 int ret, retcode = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003226
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003227 /* here, we know that the check is complete or that it failed */
3228 if (check->result != CHK_RES_UNKNOWN)
3229 goto out_end_tcpcheck;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003230
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003231 /* 1- check for connection error, if any */
3232 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3233 goto out_end_tcpcheck;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003234
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003235 /* 2- check if we are waiting for the connection establishment. It only
3236 * happens during TCPCHK_ACT_CONNECT. */
3237 if (conn && (conn->flags & CO_FL_WAIT_XPRT))
3238 goto out;
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003239
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003240 /* 3- check for pending outgoing data. It only happens during TCPCHK_ACT_SEND. */
3241 if (conn && b_data(&check->bo)) {
3242 ret = conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
3243 if (ret <= 0) {
3244 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3245 goto out_end_tcpcheck;
3246 goto out;
3247 }
3248 if (b_data(&check->bo)) {
3249 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3250 goto out;
3251 }
3252 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003253
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003254 /* Now evaluate the tcp-check rules */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003255
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003256 /* If check->current_step is defined, we are in resume condition. For
3257 * TCPCHK_ACT_CONNECT and TCPCHK_ACT_SEND rules, we must go to the next
3258 * rule before resuming the evaluation. For TCPCHK_ACT_EXPECT, we
3259 * re-evaluate the current rule. Others cannot yield.
3260 */
3261 if (check->current_step) {
3262 if (check->current_step->action == TCPCHK_ACT_CONNECT ||
3263 check->current_step->action == TCPCHK_ACT_SEND)
3264 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
3265 else
3266 rule = check->current_step;
3267 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003268 else {
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003269 struct tcpcheck_var *var;
3270
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003271 /* First evaluation, create a session */
Gaetan Rivet13a50432020-02-21 18:13:44 +01003272 check->sess = session_new(&checks_fe, NULL, (check->server ? &check->server->obj_type : NULL));
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003273 if (!check->sess) {
3274 chunk_printf(&trash, "TCPCHK error allocating check session");
3275 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3276 goto out_end_tcpcheck;
3277 }
Gaetan Rivet13a50432020-02-21 18:13:44 +01003278 vars_init(&check->vars, SCOPE_CHECK);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003279 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003280
3281 /* Preset tcp-check variables */
3282 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
3283 struct sample smp;
3284
3285 memset(&smp, 0, sizeof(smp));
3286 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
3287 smp.data = var->data;
3288 vars_set_by_name_ifexist(var->name.ptr, var->name.len, &smp);
3289 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003290 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003291
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003292 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003293 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003294
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003295 switch (rule->action) {
3296 case TCPCHK_ACT_CONNECT:
3297 check->current_step = rule;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003298
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003299 /* close but not release yet previous connection */
3300 if (check->cs) {
3301 cs_close(check->cs);
3302 retcode = -1; /* do not reuse the fd in the caller! */
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003303 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003304 eval_ret = tcpcheck_eval_connect(check, rule);
3305 must_read = 1; last_read = 0;
3306 break;
3307 case TCPCHK_ACT_SEND:
3308 check->current_step = rule;
3309 eval_ret = tcpcheck_eval_send(check, rule);
3310 must_read = 1;
3311 break;
3312 case TCPCHK_ACT_EXPECT:
3313 check->current_step = rule;
3314 if (must_read) {
3315 if (check->proxy->timeout.check)
3316 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003317
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003318 /* If we already subscribed, then we tried to received and
3319 * failed, so there's no point trying again.
3320 */
3321 if (check->wait_list.events & SUB_RETRY_RECV)
3322 goto out;
3323 if (conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0) <= 0) {
3324 if (conn->flags & (CO_FL_ERROR|CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
3325 last_read = 1;
3326 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
3327 /* Report network errors only if we got no other data. Otherwise
3328 * we'll let the upper layers decide whether the response is OK
3329 * or not. It is very common that an RST sent by the server is
3330 * reported as an error just after the last data chunk.
3331 */
3332 goto out_end_tcpcheck;
3333 }
3334 }
3335 else {
3336 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
3337 goto out;
3338 }
3339 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003340
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003341 /* buffer full, don't wait for more data */
3342 if (b_full(&check->bi))
3343 last_read = 1;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003344
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003345 /* Check that response body is not empty... */
3346 if (!b_data(&check->bi)) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003347 if (!last_read)
3348 goto out;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003349
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003350 /* empty response */
3351 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
3352 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003353 if (rule->comment)
3354 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003355 set_server_check_status(check, rule->expect.err_status, trash.area);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003356 ret = -1;
3357 goto out_end_tcpcheck;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003358 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003359 must_read = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003360 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003361
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003362 eval_ret = tcpcheck_eval_expect(check, rule, last_read);
3363 if (eval_ret == TCPCHK_EVAL_WAIT) {
3364 check->current_step = rule->expect.head;
3365 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003366 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003367 break;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003368 case TCPCHK_ACT_ACTION_KW:
3369 /* Don't update the current step */
3370 eval_ret = tcpcheck_eval_action_kw(check, rule);
3371 break;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003372 default:
3373 /* Otherwise, just go to the next one and don't update
3374 * the current step
3375 */
3376 eval_ret = TCPCHK_EVAL_CONTINUE;
3377 break;
3378 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003379
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003380 switch (eval_ret) {
3381 case TCPCHK_EVAL_CONTINUE:
3382 break;
3383 case TCPCHK_EVAL_WAIT:
3384 goto out;
3385 case TCPCHK_EVAL_STOP:
3386 goto out_end_tcpcheck;
Baptiste Assmann248f1172018-03-01 21:49:01 +01003387 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003388 }
3389
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003390 /* All rules was evaluated */
Christopher Fauletdf38f882020-04-07 16:04:38 +02003391 if (check->current_step) {
3392 rule = check->current_step;
3393
3394 if (rule->action == TCPCHK_ACT_EXPECT) {
3395 struct buffer *msg = alloc_trash_chunk();
3396
3397 if (msg)
3398 tcpcheck_onsuccess_message(msg, check, rule, ist(NULL));
3399 set_server_check_status(check, rule->expect.ok_status,
3400 (msg ? b_head(msg) : "(tcp-check)"));
3401 free_trash_chunk(msg);
3402 }
3403 else if (rule->action == TCPCHK_ACT_CONNECT) {
3404 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
3405 enum healthcheck_status status = ((conn && ssl_sock_is_ssl(conn)) ? HCHK_STATUS_L6OK : HCHK_STATUS_L4OK);
3406
3407 set_server_check_status(check, status, msg);
3408 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003409 }
Christopher Fauletec07e382020-04-07 14:56:26 +02003410 else
3411 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003412
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003413 out_end_tcpcheck:
Willy Tarreauef91c932019-07-23 14:37:47 +02003414 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003415 chk_report_conn_err(check, errno, 0);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003416
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003417 /* cleanup before leaving */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003418 check->current_step = NULL;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003419 if (check->sess != NULL) {
Gaetan Rivet13a50432020-02-21 18:13:44 +01003420 vars_prune(&check->vars, check->sess, NULL);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003421 session_free(check->sess);
3422 check->sess = NULL;
3423 }
3424 out:
3425 return retcode;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003426}
3427
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003428static const char *init_check(struct check *check, int type)
Simon Hormanb1900d52015-01-30 11:22:54 +09003429{
3430 check->type = type;
3431
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003432 b_reset(&check->bi); check->bi.size = global.tune.chksize;
3433 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Simon Hormanb1900d52015-01-30 11:22:54 +09003434
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003435 check->bi.area = calloc(check->bi.size, sizeof(char));
3436 check->bo.area = calloc(check->bo.size, sizeof(char));
3437
3438 if (!check->bi.area || !check->bo.area)
Simon Hormanb1900d52015-01-30 11:22:54 +09003439 return "out of memory while allocating check buffer";
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003440
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003441 check->wait_list.tasklet = tasklet_new();
3442 if (!check->wait_list.tasklet)
Ilya Shipitsind4259502020-04-08 01:07:56 +05003443 return "out of memory while allocating check tasklet";
Willy Tarreau4f6516d2018-12-19 13:59:17 +01003444 check->wait_list.events = 0;
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003445 check->wait_list.tasklet->process = event_srv_chk_io;
3446 check->wait_list.tasklet->context = check;
Simon Hormanb1900d52015-01-30 11:22:54 +09003447 return NULL;
3448}
3449
Simon Hormanbfb5d332015-01-30 11:22:55 +09003450void free_check(struct check *check)
3451{
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003452 task_destroy(check->task);
3453 if (check->wait_list.tasklet)
3454 tasklet_free(check->wait_list.tasklet);
3455
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003456 free(check->bi.area);
3457 free(check->bo.area);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003458 if (check->cs) {
3459 free(check->cs->conn);
3460 check->cs->conn = NULL;
3461 cs_free(check->cs);
3462 check->cs = NULL;
3463 }
Simon Hormanbfb5d332015-01-30 11:22:55 +09003464}
3465
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003466static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
3467{
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003468 struct logformat_node *lf, *lfb;
3469
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003470 if (!rule)
3471 return;
3472
3473 free(rule->comment);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003474 switch (rule->action) {
3475 case TCPCHK_ACT_SEND:
3476 switch (rule->send.type) {
3477 case TCPCHK_SEND_STRING:
3478 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003479 free(rule->send.data.ptr);
3480 break;
3481 case TCPCHK_SEND_STRING_LF:
3482 case TCPCHK_SEND_BINARY_LF:
3483 list_for_each_entry_safe(lf, lfb, &rule->send.fmt, list) {
3484 LIST_DEL(&lf->list);
3485 release_sample_expr(lf->expr);
3486 free(lf->arg);
3487 free(lf);
3488 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003489 break;
3490 case TCPCHK_SEND_UNDEF:
3491 break;
3492 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003493 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003494 case TCPCHK_ACT_EXPECT:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003495 list_for_each_entry_safe(lf, lfb, &rule->expect.onerror_fmt, list) {
3496 LIST_DEL(&lf->list);
3497 release_sample_expr(lf->expr);
3498 free(lf->arg);
3499 free(lf);
3500 }
3501 list_for_each_entry_safe(lf, lfb, &rule->expect.onsuccess_fmt, list) {
3502 LIST_DEL(&lf->list);
3503 release_sample_expr(lf->expr);
3504 free(lf->arg);
3505 free(lf);
3506 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003507 release_sample_expr(rule->expect.status_expr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003508 switch (rule->expect.type) {
3509 case TCPCHK_EXPECT_STRING:
3510 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003511 free(rule->expect.data.ptr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003512 break;
3513 case TCPCHK_EXPECT_REGEX:
3514 case TCPCHK_EXPECT_REGEX_BINARY:
3515 regex_free(rule->expect.regex);
3516 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003517 case TCPCHK_EXPECT_CUSTOM:
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003518 case TCPCHK_EXPECT_UNDEF:
3519 break;
3520 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003521 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003522 case TCPCHK_ACT_CONNECT:
Christopher Faulet79b31d42020-03-30 13:00:05 +02003523 free(rule->connect.sni);
Christopher Faulet98572322020-03-30 13:16:44 +02003524 free(rule->connect.alpn);
Christopher Fauletb7d30092020-03-30 15:19:03 +02003525 release_sample_expr(rule->connect.port_expr);
Christopher Faulet79b31d42020-03-30 13:00:05 +02003526 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003527 case TCPCHK_ACT_COMMENT:
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003528 break;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01003529 case TCPCHK_ACT_ACTION_KW:
3530 free(rule->action_kw.rule);
3531 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003532 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003533
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003534 if (in_pool)
3535 pool_free(pool_head_tcpcheck_rule, rule);
3536 else
3537 free(rule);
3538}
3539
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003540
Christopher Fauletce355072020-04-02 11:44:39 +02003541static struct tcpcheck_var *tcpcheck_var_create(const char *name)
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003542{
3543 struct tcpcheck_var *var = NULL;
3544
3545 var = calloc(1, sizeof(*var));
3546 if (var == NULL)
3547 return NULL;
3548
3549 var->name = ist2(strdup(name), strlen(name));
3550 if (var->name.ptr == NULL) {
3551 free(var);
3552 return NULL;
3553 }
3554
3555 LIST_INIT(&var->list);
3556 return var;
3557}
3558
3559static void tcpcheck_var_release(struct tcpcheck_var *var)
3560{
3561 if (!var)
3562 return;
3563
3564 free(var->name.ptr);
3565 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
3566 free(var->data.u.str.area);
3567 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
3568 free(var->data.u.meth.str.area);
3569 free(var);
3570}
3571
3572int dup_tcpcheck_vars(struct list *dst, struct list *src)
3573{
3574 struct tcpcheck_var *var, *new = NULL;
3575
3576 list_for_each_entry(var, src, list) {
3577 new = tcpcheck_var_create(var->name.ptr);
3578 if (!new)
3579 goto error;
3580 new->data.type = var->data.type;
3581 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
3582 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3583 goto error;
3584 if (var->data.type == SMP_T_STR)
3585 new->data.u.str.area[new->data.u.str.data] = 0;
3586 }
3587 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
3588 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3589 goto error;
3590 new->data.u.str.area[new->data.u.str.data] = 0;
3591 new->data.u.meth.meth = var->data.u.meth.meth;
3592 }
3593 else
3594 new->data.u = var->data.u;
3595 LIST_ADDQ(dst, &new->list);
3596 }
3597 return 1;
3598
3599 error:
3600 free(new);
3601 return 0;
3602}
3603
3604static void free_tcpcheck_vars(struct list *vars)
3605{
3606 struct tcpcheck_var *var, *back;
3607
3608 list_for_each_entry_safe(var, back, vars, list) {
3609 LIST_DEL(&var->list);
3610 tcpcheck_var_release(var);
3611 }
3612}
3613
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003614void email_alert_free(struct email_alert *alert)
3615{
3616 struct tcpcheck_rule *rule, *back;
3617
3618 if (!alert)
3619 return;
3620
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003621 if (alert->rules.list) {
3622 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
3623 LIST_DEL(&rule->list);
3624 free_tcpcheck(rule, 1);
3625 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003626 free_tcpcheck_vars(&alert->rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003627 free(alert->rules.list);
3628 alert->rules.list = NULL;
Christopher Fauletde1a75b2017-10-23 15:38:19 +02003629 }
Willy Tarreaubafbe012017-11-24 17:34:44 +01003630 pool_free(pool_head_email_alert, alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003631}
3632
Olivier Houchard9f6af332018-05-25 14:04:04 +02003633static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003634{
Olivier Houchard9f6af332018-05-25 14:04:04 +02003635 struct check *check = context;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003636 struct email_alertq *q;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003637 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003638
3639 q = container_of(check, typeof(*q), check);
3640
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003641 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003642 while (1) {
3643 if (!(check->state & CHK_ST_ENABLED)) {
3644 if (LIST_ISEMPTY(&q->email_alerts)) {
3645 /* All alerts processed, queue the task */
3646 t->expire = TICK_ETERNITY;
3647 task_queue(t);
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003648 goto end;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003649 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003650
3651 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003652 LIST_DEL(&alert->list);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003653 t->expire = now_ms;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003654 check->tcpcheck_rules = &alert->rules;
Olivier Houchard0923fa42019-01-11 18:43:04 +01003655 check->status = HCHK_STATUS_INI;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003656 check->state |= CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003657 }
3658
Olivier Houchard9f6af332018-05-25 14:04:04 +02003659 process_chk(t, context, state);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003660 if (check->state & CHK_ST_INPROGRESS)
3661 break;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003662
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003663 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003664 email_alert_free(alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003665 check->tcpcheck_rules = NULL;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003666 check->server = NULL;
3667 check->state &= ~CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003668 }
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003669 end:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003670 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003671 return t;
3672}
3673
Christopher Faulet0108bb32017-10-20 21:34:32 +02003674/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
3675 *
3676 * The function returns 1 in success case, otherwise, it returns 0 and err is
3677 * filled.
3678 */
3679int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003680{
Christopher Faulet0108bb32017-10-20 21:34:32 +02003681 struct mailer *mailer;
3682 struct email_alertq *queues;
3683 const char *err_str;
3684 int i = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003685
Christopher Faulet0108bb32017-10-20 21:34:32 +02003686 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
3687 memprintf(err, "out of memory while allocating mailer alerts queues");
mildis5ab01cb2018-10-02 16:46:34 +02003688 goto fail_no_queue;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003689 }
3690
Christopher Faulet0108bb32017-10-20 21:34:32 +02003691 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
3692 struct email_alertq *q = &queues[i];
3693 struct check *check = &q->check;
3694 struct task *t;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003695
3696 LIST_INIT(&q->email_alerts);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003697 HA_SPIN_INIT(&q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003698 check->inter = mls->timeout.mail;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003699 check->rise = DEF_AGENT_RISETIME;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01003700 check->proxy = p;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003701 check->fall = DEF_AGENT_FALLTIME;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003702 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
3703 memprintf(err, "%s", err_str);
3704 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003705 }
3706
3707 check->xprt = mailer->xprt;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003708 check->addr = mailer->addr;
Christopher Fauletb797ae12018-03-27 15:35:35 +02003709 check->port = get_host_port(&mailer->addr);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003710
Emeric Brunc60def82017-09-27 14:59:38 +02003711 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003712 memprintf(err, "out of memory while allocating mailer alerts task");
3713 goto error;
3714 }
3715
3716 check->task = t;
3717 t->process = process_email_alert;
3718 t->context = check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003719
Christopher Faulet0108bb32017-10-20 21:34:32 +02003720 /* check this in one ms */
3721 t->expire = TICK_ETERNITY;
3722 check->start = now;
3723 task_queue(t);
3724 }
3725
3726 mls->users++;
3727 free(p->email_alert.mailers.name);
3728 p->email_alert.mailers.m = mls;
3729 p->email_alert.queues = queues;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003730 return 0;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003731
3732 error:
3733 for (i = 0; i < mls->count; i++) {
3734 struct email_alertq *q = &queues[i];
3735 struct check *check = &q->check;
3736
Christopher Faulet0108bb32017-10-20 21:34:32 +02003737 free_check(check);
3738 }
3739 free(queues);
mildis5ab01cb2018-10-02 16:46:34 +02003740 fail_no_queue:
Christopher Faulet0108bb32017-10-20 21:34:32 +02003741 return 1;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003742}
3743
3744
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003745static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003746{
Gaetan Rivet4038b942020-02-26 16:19:40 +01003747 struct tcpcheck_rule *tcpcheck, *prev_check;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003748 struct tcpcheck_expect *expect;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003749
Willy Tarreaubafbe012017-11-24 17:34:44 +01003750 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003751 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003752 memset(tcpcheck, 0, sizeof(*tcpcheck));
Gaetan Rivetb616add2020-02-07 15:37:17 +01003753 tcpcheck->action = TCPCHK_ACT_EXPECT;
3754
3755 expect = &tcpcheck->expect;
3756 expect->type = TCPCHK_EXPECT_STRING;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003757 LIST_INIT(&expect->onerror_fmt);
3758 LIST_INIT(&expect->onsuccess_fmt);
Christopher Fauletec07e382020-04-07 14:56:26 +02003759 expect->ok_status = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003760 expect->err_status = HCHK_STATUS_L7RSP;
3761 expect->tout_status = HCHK_STATUS_L7TOUT;
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003762 expect->data = ist2(strdup(str), strlen(str));
3763 if (!expect->data.ptr) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003764 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003765 return 0;
3766 }
3767
Gaetan Rivet4038b942020-02-26 16:19:40 +01003768 /* All tcp-check expect points back to the first inverse expect rule
3769 * in a chain of one or more expect rule, potentially itself.
3770 */
Gaetan Rivetb616add2020-02-07 15:37:17 +01003771 tcpcheck->expect.head = tcpcheck;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003772 list_for_each_entry_rev(prev_check, rules->list, list) {
Gaetan Rivet4038b942020-02-26 16:19:40 +01003773 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet12d57402020-04-10 09:58:42 +02003774 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
Gaetan Rivetb616add2020-02-07 15:37:17 +01003775 tcpcheck->expect.head = prev_check;
Gaetan Rivet4038b942020-02-26 16:19:40 +01003776 continue;
3777 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003778 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet4038b942020-02-26 16:19:40 +01003779 break;
3780 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003781 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003782 return 1;
3783}
3784
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003785static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003786{
3787 struct tcpcheck_rule *tcpcheck;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003788 struct tcpcheck_send *send;
Willy Tarreau64345aa2016-08-10 19:29:09 +02003789 const char *in;
3790 char *dst;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003791 int i;
3792
Willy Tarreaubafbe012017-11-24 17:34:44 +01003793 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003794 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003795 memset(tcpcheck, 0, sizeof(*tcpcheck));
3796 tcpcheck->action = TCPCHK_ACT_SEND;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003797
3798 send = &tcpcheck->send;
3799 send->type = TCPCHK_SEND_STRING;
3800
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003801 for (i = 0; strs[i]; i++)
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003802 send->data.len += strlen(strs[i]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003803
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003804 send->data.ptr = malloc(send->data.len + 1);
3805 if (!isttest(send->data)) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003806 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003807 return 0;
3808 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003809
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003810 dst = send->data.ptr;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003811 for (i = 0; strs[i]; i++)
Willy Tarreau64345aa2016-08-10 19:29:09 +02003812 for (in = strs[i]; (*dst = *in++); dst++);
3813 *dst = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003814
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003815 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003816 return 1;
3817}
3818
Christopher Faulet0108bb32017-10-20 21:34:32 +02003819static int enqueue_one_email_alert(struct proxy *p, struct server *s,
3820 struct email_alertq *q, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003821{
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003822 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003823 struct tcpcheck_rule *tcpcheck;
3824 struct check *check = &q->check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003825
Willy Tarreaubafbe012017-11-24 17:34:44 +01003826 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003827 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003828 LIST_INIT(&alert->list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003829 alert->rules.flags = 0;
3830 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
3831 if (!alert->rules.list)
3832 goto error;
3833 LIST_INIT(alert->rules.list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003834 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
Christopher Faulet0108bb32017-10-20 21:34:32 +02003835 alert->srv = s;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003836
Willy Tarreaubafbe012017-11-24 17:34:44 +01003837 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003838 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003839 memset(tcpcheck, 0, sizeof(*tcpcheck));
3840 tcpcheck->action = TCPCHK_ACT_CONNECT;
3841 tcpcheck->comment = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003842
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003843 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003844
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003845 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003846 goto error;
3847
3848 {
3849 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003850 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003851 goto error;
3852 }
3853
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003854 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003855 goto error;
3856
3857 {
3858 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003859 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003860 goto error;
3861 }
3862
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003863 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003864 goto error;
3865
3866 {
3867 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003868 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003869 goto error;
3870 }
3871
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003872 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003873 goto error;
3874
3875 {
3876 const char * const strs[2] = { "DATA\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003877 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003878 goto error;
3879 }
3880
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003881 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003882 goto error;
3883
3884 {
3885 struct tm tm;
3886 char datestr[48];
3887 const char * const strs[18] = {
Pieter Baauw5e0964e2016-02-13 16:27:35 +01003888 "From: ", p->email_alert.from, "\r\n",
3889 "To: ", p->email_alert.to, "\r\n",
3890 "Date: ", datestr, "\r\n",
3891 "Subject: [HAproxy Alert] ", msg, "\r\n",
3892 "\r\n",
3893 msg, "\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003894 "\r\n",
Pieter Baauwed35c372015-07-22 19:51:54 +02003895 ".\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003896 NULL
3897 };
3898
3899 get_localtime(date.tv_sec, &tm);
3900
3901 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
3902 goto error;
3903 }
3904
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003905 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003906 goto error;
3907 }
3908
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003909 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003910 goto error;
3911
3912 {
3913 const char * const strs[2] = { "QUIT\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003914 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003915 goto error;
3916 }
3917
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003918 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003919 goto error;
3920
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003921 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003922 task_wakeup(check->task, TASK_WOKEN_MSG);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003923 LIST_ADDQ(&q->email_alerts, &alert->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003924 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003925 return 1;
3926
3927error:
3928 email_alert_free(alert);
3929 return 0;
3930}
3931
Christopher Faulet0108bb32017-10-20 21:34:32 +02003932static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003933{
3934 int i;
3935 struct mailer *mailer;
3936
3937 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
3938 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003939 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003940 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003941 return;
3942 }
3943 }
3944
3945 return;
3946}
3947
3948/*
3949 * Send email alert if configured.
3950 */
Simon Horman64e34162015-02-06 11:11:57 +09003951void send_email_alert(struct server *s, int level, const char *format, ...)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003952{
3953 va_list argp;
3954 char buf[1024];
3955 int len;
3956 struct proxy *p = s->proxy;
3957
Christopher Faulet0108bb32017-10-20 21:34:32 +02003958 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003959 return;
3960
3961 va_start(argp, format);
3962 len = vsnprintf(buf, sizeof(buf), format, argp);
3963 va_end(argp);
3964
Thierry FOURNIER62c8a212017-02-09 12:19:27 +01003965 if (len < 0 || len >= sizeof(buf)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01003966 ha_alert("Email alert [%s] could not format message\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003967 return;
3968 }
3969
Christopher Faulet0108bb32017-10-20 21:34:32 +02003970 enqueue_email_alert(p, s, buf);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003971}
3972
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003973/*
3974 * Return value:
3975 * the port to be used for the health check
3976 * 0 in case no port could be found for the check
3977 */
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003978static int srv_check_healthcheck_port(struct check *chk)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003979{
3980 int i = 0;
3981 struct server *srv = NULL;
3982
3983 srv = chk->server;
3984
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02003985 /* by default, we use the health check port ocnfigured */
3986 if (chk->port > 0)
3987 return chk->port;
3988
3989 /* try to get the port from check_core.addr if check.port not set */
3990 i = get_host_port(&chk->addr);
3991 if (i > 0)
3992 return i;
3993
3994 /* try to get the port from server address */
3995 /* prevent MAPPORTS from working at this point, since checks could
3996 * not be performed in such case (MAPPORTS impose a relative ports
3997 * based on live traffic)
3998 */
3999 if (srv->flags & SRV_F_MAPPORTS)
4000 return 0;
Willy Tarreau04276f32017-01-06 17:41:29 +01004001
4002 i = srv->svc_port; /* by default */
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004003 if (i > 0)
4004 return i;
4005
4006 return 0;
4007}
4008
Willy Tarreau172f5ce2018-11-26 11:21:50 +01004009REGISTER_POST_CHECK(start_checks);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02004010
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004011static int check_proxy_tcpcheck(struct proxy *px)
4012{
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004013 struct tcpcheck_rule *chk, *back;
4014 char *comment = NULL;
4015 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004016 int ret = 0;
4017
Christopher Faulet404f9192020-04-09 23:13:54 +02004018 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK)
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004019 goto out;
4020
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004021 if (!px->tcpcheck_rules.list) {
Christopher Faulet404f9192020-04-09 23:13:54 +02004022 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4023 ret |= ERR_ALERT | ERR_FATAL;
4024 goto out;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004025 }
4026
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004027 /* If there is no connect rule preceeding all send / expect rules, an
4028 * implicit one is inserted before all others
4029 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004030 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004031 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4032 chk = calloc(1, sizeof(*chk));
4033 if (!chk) {
4034 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4035 "(out of memory).\n", px->id);
4036 ret |= ERR_ALERT | ERR_FATAL;
4037 goto out;
4038 }
4039 chk->action = TCPCHK_ACT_CONNECT;
Christopher Fauletbb591a12020-04-01 16:52:17 +02004040 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004041 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004042 }
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004043
4044 /* Now remove comment rules */
4045 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4046 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4047 free(comment);
4048 comment = NULL;
4049 }
4050
4051 prev_action = chk->action;
4052 switch (chk->action) {
4053 case TCPCHK_ACT_COMMENT:
4054 free(comment);
4055 comment = chk->comment;
4056 LIST_DEL(&chk->list);
4057 free(chk);
4058 break;
4059 case TCPCHK_ACT_CONNECT:
4060 if (!chk->comment && comment)
4061 chk->comment = strdup(comment);
4062 /* fall though */
4063 case TCPCHK_ACT_ACTION_KW:
4064 free(comment);
4065 comment = NULL;
4066 break;
4067 case TCPCHK_ACT_SEND:
4068 case TCPCHK_ACT_EXPECT:
4069 if (!chk->comment && comment)
4070 chk->comment = strdup(comment);
4071 break;
4072 }
4073 }
4074 free(comment);
4075 comment = NULL;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004076
4077 out:
4078 return ret;
4079}
4080
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004081static int init_srv_check(struct server *srv)
4082{
4083 const char *err;
4084 struct tcpcheck_rule *r;
4085 int ret = 0;
4086
4087 if (!srv->do_check)
4088 goto out;
4089
4090
4091 /* If neither a port nor an addr was specified and no check transport
4092 * layer is forced, then the transport layer used by the checks is the
4093 * same as for the production traffic. Otherwise we use raw_sock by
4094 * default, unless one is specified.
4095 */
4096 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4097 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4098 srv->check.use_ssl = srv->use_ssl;
4099 srv->check.xprt = srv->xprt;
4100 }
4101 else if (srv->check.use_ssl == 1)
4102 srv->check.xprt = xprt_get(XPRT_SSL);
4103
4104 srv->check.send_proxy |= (srv->pp_opts);
4105 }
4106
4107 /* validate <srv> server health-check settings */
4108
4109 /* We need at least a service port, a check port or the first tcp-check
4110 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4111 */
4112 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4113 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4114 goto init;
4115
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004116 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004117 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4118 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4119 ret |= ERR_ALERT | ERR_ABORT;
4120 goto out;
4121 }
4122
4123 /* search the first action (connect / send / expect) in the list */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004124 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
Christopher Faulet5c288742020-03-31 08:15:58 +02004125 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004126 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4127 "nor tcp_check rule 'connect' with port information.\n",
4128 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4129 ret |= ERR_ALERT | ERR_ABORT;
4130 goto out;
4131 }
4132
4133 /* scan the tcp-check ruleset to ensure a port has been configured */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004134 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
Christopher Faulet5c288742020-03-31 08:15:58 +02004135 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004136 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4137 "and a tcp_check rule 'connect' with no port information.\n",
4138 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4139 ret |= ERR_ALERT | ERR_ABORT;
4140 goto out;
4141 }
4142 }
4143
4144 init:
4145 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4146 if (err) {
4147 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4148 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4149 ret |= ERR_ALERT | ERR_ABORT;
4150 goto out;
4151 }
4152 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4153 global.maxsock++;
4154
4155 out:
4156 return ret;
4157}
4158
4159static int init_srv_agent_check(struct server *srv)
4160{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004161 struct tcpcheck_rule *chk;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004162 const char *err;
4163 int ret = 0;
4164
4165 if (!srv->do_agent)
4166 goto out;
4167
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004168 /* If there is no connect rule preceeding all send / expect rules, an
4169 * implicit one is inserted before all others.
4170 */
4171 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4172 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4173 chk = calloc(1, sizeof(*chk));
4174 if (!chk) {
4175 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4176 " to agent-check for server '%s' (out of memory).\n",
4177 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4178 ret |= ERR_ALERT | ERR_FATAL;
4179 goto out;
4180 }
4181 chk->action = TCPCHK_ACT_CONNECT;
4182 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4183 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
4184 }
4185
4186
4187 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004188 if (err) {
4189 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4190 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4191 ret |= ERR_ALERT | ERR_ABORT;
4192 goto out;
4193 }
4194
4195 if (!srv->agent.inter)
4196 srv->agent.inter = srv->check.inter;
4197
4198 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4199 global.maxsock++;
4200
4201 out:
4202 return ret;
4203}
4204
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004205void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004206{
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02004207 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004208 px->tcpcheck_rules.flags = 0;
4209 px->tcpcheck_rules.list = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004210}
4211
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004212static void deinit_srv_check(struct server *srv)
4213{
Christopher Fauletce8111e2020-04-06 15:04:11 +02004214 if (srv->check.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004215 free_check(&srv->check);
Christopher Fauletce8111e2020-04-06 15:04:11 +02004216 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
4217 srv->do_check = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004218}
4219
4220
4221static void deinit_srv_agent_check(struct server *srv)
4222{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004223 if (srv->agent.tcpcheck_rules) {
4224 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
4225 free(srv->agent.tcpcheck_rules);
4226 srv->agent.tcpcheck_rules = NULL;
4227 }
4228
4229 if (srv->agent.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004230 free_check(&srv->agent);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004231
4232 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
4233 srv->do_agent = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004234}
4235
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004236static void deinit_tcpchecks()
4237{
4238 struct tcpcheck_ruleset *rs, *rsb;
4239 struct tcpcheck_rule *r, *rb;
4240
4241 list_for_each_entry_safe(rs, rsb, &tcpchecks_list, list) {
4242 LIST_DEL(&rs->list);
4243 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4244 LIST_DEL(&r->list);
4245 free_tcpcheck(r, 0);
4246 }
4247 free(rs->name);
4248 free(rs);
4249 }
4250}
4251
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004252
4253REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004254REGISTER_POST_SERVER_CHECK(init_srv_check);
4255REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
4256
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004257REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004258REGISTER_SERVER_DEINIT(deinit_srv_check);
4259REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004260REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004261
Christopher Faulet404f9192020-04-09 23:13:54 +02004262static struct tcpcheck_ruleset *tcpcheck_ruleset_lookup(const char *name)
4263{
4264 struct tcpcheck_ruleset *rs;
4265
4266 list_for_each_entry(rs, &tcpchecks_list, list) {
4267 if (strcmp(rs->name, name) == 0)
4268 return rs;
4269 }
4270 return NULL;
4271}
4272
4273static struct tcpcheck_ruleset *tcpcheck_ruleset_create(const char *name)
4274{
4275 struct tcpcheck_ruleset *rs;
4276
4277 rs = calloc(1, sizeof(*rs));
4278 if (rs == NULL)
4279 return NULL;
4280
4281 rs->name = strdup(name);
4282 if (rs->name == NULL) {
4283 free(rs);
4284 return NULL;
4285 }
4286
4287 LIST_INIT(&rs->list);
4288 LIST_INIT(&rs->rules);
4289 LIST_ADDQ(&tcpchecks_list, &rs->list);
4290 return rs;
4291}
4292
4293static void tcpcheck_ruleset_release(struct tcpcheck_ruleset *rs)
4294{
4295 struct tcpcheck_rule *r, *rb;
4296 if (!rs)
4297 return;
4298
4299 LIST_DEL(&rs->list);
4300 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4301 LIST_DEL(&r->list);
4302 free_tcpcheck(r, 0);
4303 }
4304 free(rs->name);
4305 free(rs);
4306}
4307
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004308/* extracts check payload at a fixed position and length */
4309static int
4310smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
4311{
4312 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
4313 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
4314 struct server *srv = (smp->sess ? objt_server(smp->sess->origin) : NULL);
4315 struct buffer *buf;
4316
4317 if (!srv || !srv->do_check)
4318 return 0;
4319
4320 buf = &srv->check.bi;
4321 if (buf_offset > b_data(buf))
4322 goto no_match;
4323 if (buf_offset + buf_size > b_data(buf))
4324 buf_size = 0;
4325
4326 /* init chunk as read only */
4327 smp->data.type = SMP_T_STR;
4328 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
4329 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
4330
4331 return 1;
4332
4333 no_match:
4334 smp->flags = 0;
4335 return 0;
4336}
4337
4338static struct sample_fetch_kw_list smp_kws = {ILH, {
4339 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
4340 { /* END */ },
4341}};
4342
4343INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
4344
4345
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004346struct action_kw_list tcp_check_keywords = {
4347 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
4348};
4349
4350/* Return the struct action_kw associated to a keyword */
4351static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
4352{
4353 return action_lookup(&tcp_check_keywords.list, kw);
4354}
4355
4356static void action_kw_tcp_check_build_list(struct buffer *chk)
4357{
4358 action_build_list(&tcp_check_keywords.list, chk);
4359}
4360
4361/* Create a tcp-check rule resulting from parsing a custom keyword. */
4362static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004363 struct list *rules, struct action_kw *kw,
4364 const char *file, int line, char **errmsg)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004365{
4366 struct tcpcheck_rule *chk = NULL;
4367 struct act_rule *actrule = NULL;
4368
4369 actrule = calloc(1, sizeof(*actrule));
4370 if (!actrule) {
4371 memprintf(errmsg, "out of memory");
4372 goto error;
4373 }
4374 actrule->kw = kw;
4375 actrule->from = ACT_F_TCP_CHK;
4376
4377 cur_arg++;
4378 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
4379 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
4380 goto error;
4381 }
4382
4383 chk = calloc(1, sizeof(*chk));
4384 if (!chk) {
4385 memprintf(errmsg, "out of memory");
4386 goto error;
4387 }
4388 chk->action = TCPCHK_ACT_ACTION_KW;
4389 chk->action_kw.rule = actrule;
4390 return chk;
4391
4392 error:
4393 free(actrule);
4394 return NULL;
4395}
4396
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004397static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Fauletb7d30092020-03-30 15:19:03 +02004398 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004399{
4400 struct tcpcheck_rule *chk = NULL;
Christopher Faulet5c288742020-03-31 08:15:58 +02004401 struct sockaddr_storage *sk = NULL;
Christopher Faulet98572322020-03-30 13:16:44 +02004402 char *comment = NULL, *sni = NULL, *alpn = NULL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004403 struct sample_expr *port_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004404 unsigned short conn_opts = 0;
4405 long port = 0;
Christopher Faulet98572322020-03-30 13:16:44 +02004406 int alpn_len = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004407
4408 list_for_each_entry(chk, rules, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004409 if (chk->action != TCPCHK_ACT_COMMENT && chk->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004410 break;
4411 }
4412 if (&chk->list != rules && chk->action != TCPCHK_ACT_CONNECT) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004413 memprintf(errmsg, "first step MUST also be a 'connect', "
4414 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
4415 "when there is a 'connect' step in the tcp-check ruleset");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004416 goto error;
4417 }
4418
4419 cur_arg++;
4420 while (*(args[cur_arg])) {
Christopher Fauletbb591a12020-04-01 16:52:17 +02004421 if (strcmp(args[cur_arg], "default") == 0)
4422 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
Christopher Faulet5c288742020-03-31 08:15:58 +02004423 else if (strcmp(args[cur_arg], "addr") == 0) {
4424 int port1, port2;
4425 struct protocol *proto;
4426
4427 if (!*(args[cur_arg+1])) {
4428 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
4429 goto error;
4430 }
4431
4432 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
4433 if (!sk) {
4434 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
4435 goto error;
4436 }
4437
4438 proto = protocol_by_family(sk->ss_family);
4439 if (!proto || !proto->connect) {
4440 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
4441 args[cur_arg]);
4442 goto error;
4443 }
4444
4445 if (port1 != port2) {
4446 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
4447 args[cur_arg], args[cur_arg+1]);
4448 goto error;
4449 }
4450
4451 cur_arg++;
4452 }
Christopher Faulet4dce5922020-03-30 13:54:42 +02004453 else if (strcmp(args[cur_arg], "port") == 0) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004454 const char *p, *end;
4455
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004456 if (!*(args[cur_arg+1])) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004457 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004458 goto error;
4459 }
4460 cur_arg++;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004461
4462 port = 0;
4463 release_sample_expr(port_expr);
4464 p = args[cur_arg]; end = p + strlen(p);
4465 port = read_uint(&p, end);
4466 if (p != end) {
4467 int idx = 0;
4468
4469 px->conf.args.ctx = ARGC_SRV;
4470 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4471 file, line, errmsg, &px->conf.args, NULL);
4472
4473 if (!port_expr) {
4474 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
4475 goto error;
4476 }
4477 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4478 memprintf(errmsg, "error detected while parsing port expression : "
4479 " fetch method '%s' extracts information from '%s', "
4480 "none of which is available here.\n",
4481 args[cur_arg], sample_src_names(port_expr->fetch->use));
4482 goto error;
4483 }
4484 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
4485 }
4486 else if (port > 65535 || port < 1) {
4487 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
4488 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004489 goto error;
4490 }
4491 }
4492 else if (strcmp(args[cur_arg], "comment") == 0) {
4493 if (!*(args[cur_arg+1])) {
4494 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4495 goto error;
4496 }
4497 cur_arg++;
4498 free(comment);
4499 comment = strdup(args[cur_arg]);
4500 if (!comment) {
4501 memprintf(errmsg, "out of memory");
4502 goto error;
4503 }
4504 }
4505 else if (strcmp(args[cur_arg], "send-proxy") == 0)
4506 conn_opts |= TCPCHK_OPT_SEND_PROXY;
Christopher Faulet085426a2020-03-30 13:07:02 +02004507 else if (strcmp(args[cur_arg], "via-socks4") == 0)
4508 conn_opts |= TCPCHK_OPT_SOCKS4;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004509 else if (strcmp(args[cur_arg], "linger") == 0)
4510 conn_opts |= TCPCHK_OPT_LINGER;
4511#ifdef USE_OPENSSL
4512 else if (strcmp(args[cur_arg], "ssl") == 0) {
4513 px->options |= PR_O_TCPCHK_SSL;
4514 conn_opts |= TCPCHK_OPT_SSL;
4515 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02004516 else if (strcmp(args[cur_arg], "sni") == 0) {
4517 if (!*(args[cur_arg+1])) {
4518 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4519 goto error;
4520 }
4521 cur_arg++;
4522 free(sni);
4523 sni = strdup(args[cur_arg]);
4524 if (!sni) {
4525 memprintf(errmsg, "out of memory");
4526 goto error;
4527 }
4528 }
Christopher Faulet98572322020-03-30 13:16:44 +02004529 else if (strcmp(args[cur_arg], "alpn") == 0) {
4530#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
4531 free(alpn);
4532 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
4533 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
4534 goto error;
4535 }
4536 cur_arg++;
4537#else
4538 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
4539 goto error;
4540#endif
4541 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004542#endif /* USE_OPENSSL */
4543
4544 else {
Christopher Faulet5c288742020-03-31 08:15:58 +02004545 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004546#ifdef USE_OPENSSL
Christopher Faulet98572322020-03-30 13:16:44 +02004547 ", 'ssl', 'sni', 'alpn'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004548#endif /* USE_OPENSSL */
Christopher Faulet4dce5922020-03-30 13:54:42 +02004549 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004550 args[cur_arg]);
4551 goto error;
4552 }
4553 cur_arg++;
4554 }
4555
4556 chk = calloc(1, sizeof(*chk));
4557 if (!chk) {
4558 memprintf(errmsg, "out of memory");
4559 goto error;
4560 }
Gaetan Rivet06d963a2020-02-21 18:49:05 +01004561 chk->action = TCPCHK_ACT_CONNECT;
4562 chk->comment = comment;
4563 chk->connect.port = port;
4564 chk->connect.options = conn_opts;
Christopher Faulet79b31d42020-03-30 13:00:05 +02004565 chk->connect.sni = sni;
Christopher Faulet98572322020-03-30 13:16:44 +02004566 chk->connect.alpn = alpn;
4567 chk->connect.alpn_len= alpn_len;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004568 chk->connect.port_expr= port_expr;
Christopher Faulet5c288742020-03-31 08:15:58 +02004569 if (sk)
4570 chk->connect.addr = *sk;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004571 return chk;
4572
4573 error:
Christopher Faulet98572322020-03-30 13:16:44 +02004574 free(alpn);
Christopher Faulet79b31d42020-03-30 13:00:05 +02004575 free(sni);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004576 free(comment);
Christopher Fauletb7d30092020-03-30 15:19:03 +02004577 release_sample_expr(port_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004578 return NULL;
4579}
4580
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004581static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004582 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004583{
4584 struct tcpcheck_rule *chk = NULL;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004585 char *comment = NULL, *data = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004586 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004587
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004588 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004589 if (!*(args[cur_arg+1])) {
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004590 memprintf(errmsg, "'%s' expects a %s as argument",
4591 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004592 goto error;
4593 }
4594
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004595 data = args[cur_arg+1];
4596
4597 cur_arg += 2;
4598 while (*(args[cur_arg])) {
4599 if (strcmp(args[cur_arg], "comment") == 0) {
4600 if (!*(args[cur_arg+1])) {
4601 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4602 goto error;
4603 }
4604 cur_arg++;
4605 free(comment);
4606 comment = strdup(args[cur_arg]);
4607 if (!comment) {
4608 memprintf(errmsg, "out of memory");
4609 goto error;
4610 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004611 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004612 else if (strcmp(args[cur_arg], "log-format") == 0) {
4613 if (type == TCPCHK_SEND_BINARY)
4614 type = TCPCHK_SEND_BINARY_LF;
4615 else if (type == TCPCHK_SEND_STRING)
4616 type = TCPCHK_SEND_STRING_LF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004617 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004618 else {
4619 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
4620 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004621 goto error;
4622 }
4623 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004624 }
4625
4626 chk = calloc(1, sizeof(*chk));
4627 if (!chk) {
4628 memprintf(errmsg, "out of memory");
4629 goto error;
4630 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004631 chk->action = TCPCHK_ACT_SEND;
4632 chk->comment = comment;
4633 chk->send.type = type;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004634
4635 switch (chk->send.type) {
4636 case TCPCHK_SEND_STRING:
4637 chk->send.data = ist2(strdup(data), strlen(data));
4638 if (!isttest(chk->send.data)) {
4639 memprintf(errmsg, "out of memory");
4640 goto error;
4641 }
4642 break;
4643 case TCPCHK_SEND_BINARY:
4644 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
4645 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
4646 goto error;
4647 }
4648 break;
4649 case TCPCHK_SEND_STRING_LF:
4650 case TCPCHK_SEND_BINARY_LF:
4651 LIST_INIT(&chk->send.fmt);
4652 px->conf.args.ctx = ARGC_SRV;
4653 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4654 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
4655 goto error;
4656 }
4657 break;
4658 case TCPCHK_SEND_UNDEF:
4659 goto error;
4660 }
4661
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004662 return chk;
4663
4664 error:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004665 free(chk);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004666 free(comment);
4667 return NULL;
4668}
4669
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004670static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
4671 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004672{
4673 struct tcpcheck_rule *chk = NULL;
4674 char *comment = NULL;
4675
4676 if (!*(args[cur_arg+1])) {
4677 memprintf(errmsg, "expects a string as argument");
4678 goto error;
4679 }
4680 cur_arg++;
4681 comment = strdup(args[cur_arg]);
4682 if (!comment) {
4683 memprintf(errmsg, "out of memory");
4684 goto error;
4685 }
4686
4687 chk = calloc(1, sizeof(*chk));
4688 if (!chk) {
4689 memprintf(errmsg, "out of memory");
4690 goto error;
4691 }
4692 chk->action = TCPCHK_ACT_COMMENT;
4693 chk->comment = comment;
4694 return chk;
4695
4696 error:
4697 free(comment);
4698 return NULL;
4699}
4700
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004701static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px, struct list *rules,
4702 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004703{
4704 struct tcpcheck_rule *prev_check, *chk = NULL;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004705 struct sample_expr *status_expr = NULL;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004706 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004707 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
Christopher Fauletec07e382020-04-07 14:56:26 +02004708 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004709 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
4710 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004711 long min_recv = -1;
4712 int inverse = 0, with_capture = 0;
4713
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004714 str = on_success_msg = on_error_msg = comment = pattern = NULL;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004715 if (!*(args[cur_arg+1])) {
4716 memprintf(errmsg, "expects at least a matching pattern as arguments");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004717 goto error;
4718 }
4719
4720 cur_arg++;
4721 while (*(args[cur_arg])) {
4722 int in_pattern = 0;
4723
4724 rescan:
4725 if (strcmp(args[cur_arg], "min-recv") == 0) {
4726 if (in_pattern) {
4727 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4728 goto error;
4729 }
4730 if (!*(args[cur_arg+1])) {
4731 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
4732 goto error;
4733 }
4734 /* Use an signed integer here because of chksize */
4735 cur_arg++;
4736 min_recv = atol(args[cur_arg]);
4737 if (min_recv < -1 || min_recv > INT_MAX) {
4738 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
4739 goto error;
4740 }
4741 }
4742 else if (*(args[cur_arg]) == '!') {
4743 in_pattern = 1;
4744 while (*(args[cur_arg]) == '!') {
4745 inverse = !inverse;
4746 args[cur_arg]++;
4747 }
4748 if (!*(args[cur_arg]))
4749 cur_arg++;
4750 goto rescan;
4751 }
4752 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "binary") == 0 ||
4753 strcmp(args[cur_arg], "rstring") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
4754 if (type != TCPCHK_EXPECT_UNDEF) {
4755 memprintf(errmsg, "only on pattern expected");
4756 goto error;
4757 }
4758 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING :
4759 ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY :
4760 ((*(args[cur_arg]+1) == 's') ? TCPCHK_EXPECT_REGEX : TCPCHK_EXPECT_REGEX_BINARY)));
4761
4762 if (!*(args[cur_arg+1])) {
4763 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
4764 goto error;
4765 }
4766 cur_arg++;
4767 pattern = args[cur_arg];
4768 }
Christopher Faulet9e6ed152020-04-03 15:24:06 +02004769 else if (strcmp(args[cur_arg], "custom") == 0) {
4770 if (in_pattern) {
4771 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4772 goto error;
4773 }
4774 if (type != TCPCHK_EXPECT_UNDEF) {
4775 memprintf(errmsg, "only on pattern expected");
4776 goto error;
4777 }
4778 type = TCPCHK_EXPECT_CUSTOM;
4779 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004780 else if (strcmp(args[cur_arg], "comment") == 0) {
4781 if (in_pattern) {
4782 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4783 goto error;
4784 }
4785 if (!*(args[cur_arg+1])) {
4786 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4787 goto error;
4788 }
4789 cur_arg++;
4790 free(comment);
4791 comment = strdup(args[cur_arg]);
4792 if (!comment) {
4793 memprintf(errmsg, "out of memory");
4794 goto error;
4795 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004796 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004797 else if (strcmp(args[cur_arg], "on-success") == 0) {
4798 if (in_pattern) {
4799 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4800 goto error;
4801 }
4802 if (!*(args[cur_arg+1])) {
4803 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4804 goto error;
4805 }
4806 cur_arg++;
4807 free(on_success_msg);
4808 on_success_msg = strdup(args[cur_arg]);
4809 if (!on_success_msg) {
4810 memprintf(errmsg, "out of memory");
4811 goto error;
4812 }
4813 }
4814 else if (strcmp(args[cur_arg], "on-error") == 0) {
4815 if (in_pattern) {
4816 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4817 goto error;
4818 }
4819 if (!*(args[cur_arg+1])) {
4820 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4821 goto error;
4822 }
4823 cur_arg++;
4824 free(on_error_msg);
4825 on_error_msg = strdup(args[cur_arg]);
4826 if (!on_error_msg) {
4827 memprintf(errmsg, "out of memory");
4828 goto error;
4829 }
Christopher Fauletec07e382020-04-07 14:56:26 +02004830 }
4831 else if (strcmp(args[cur_arg], "ok-status") == 0) {
4832 if (in_pattern) {
4833 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4834 goto error;
4835 }
4836 if (!*(args[cur_arg+1])) {
4837 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4838 goto error;
4839 }
4840 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
4841 ok_st = HCHK_STATUS_L7OKD;
4842 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
4843 ok_st = HCHK_STATUS_L7OKCD;
4844 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
4845 ok_st = HCHK_STATUS_L6OK;
4846 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
4847 ok_st = HCHK_STATUS_L4OK;
4848 else {
4849 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
4850 args[cur_arg], args[cur_arg+1]);
4851 goto error;
4852 }
4853 cur_arg++;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004854 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004855 else if (strcmp(args[cur_arg], "error-status") == 0) {
4856 if (in_pattern) {
4857 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4858 goto error;
4859 }
4860 if (!*(args[cur_arg+1])) {
4861 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4862 goto error;
4863 }
4864 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
4865 err_st = HCHK_STATUS_L7RSP;
4866 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
4867 err_st = HCHK_STATUS_L7STS;
4868 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
4869 err_st = HCHK_STATUS_L6RSP;
4870 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
4871 err_st = HCHK_STATUS_L4CON;
4872 else {
4873 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
4874 args[cur_arg], args[cur_arg+1]);
4875 goto error;
4876 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004877 cur_arg++;
4878 }
4879 else if (strcmp(args[cur_arg], "status-code") == 0) {
4880 int idx = 0;
4881
4882 if (in_pattern) {
4883 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4884 goto error;
4885 }
4886 if (!*(args[cur_arg+1])) {
4887 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
4888 goto error;
4889 }
4890
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004891 cur_arg++;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004892 release_sample_expr(status_expr);
4893 px->conf.args.ctx = ARGC_SRV;
4894 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4895 file, line, errmsg, &px->conf.args, NULL);
4896 if (!status_expr) {
4897 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
4898 goto error;
4899 }
4900 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4901 memprintf(errmsg, "error detected while parsing status-code expression : "
4902 " fetch method '%s' extracts information from '%s', "
4903 "none of which is available here.\n",
4904 args[cur_arg], sample_src_names(status_expr->fetch->use));
4905 goto error;
4906 }
4907 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004908 }
4909 else if (strcmp(args[cur_arg], "tout-status") == 0) {
4910 if (in_pattern) {
4911 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
4912 goto error;
4913 }
4914 if (!*(args[cur_arg+1])) {
4915 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
4916 goto error;
4917 }
4918 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
4919 tout_st = HCHK_STATUS_L7TOUT;
4920 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
4921 tout_st = HCHK_STATUS_L6TOUT;
4922 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
4923 tout_st = HCHK_STATUS_L4TOUT;
4924 else {
4925 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
4926 args[cur_arg], args[cur_arg+1]);
4927 goto error;
4928 }
4929 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004930 }
4931 else {
4932 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
4933 " or comment but got '%s' as argument.", args[cur_arg]);
4934 goto error;
4935 }
4936
4937 cur_arg++;
4938 }
4939
4940 if (comment) {
4941 char *p = comment;
4942
4943 while (*p) {
4944 if (*p == '\\') {
4945 p++;
4946 if (!*p || !isdigit((unsigned char)*p) ||
4947 (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
4948 memprintf(errmsg, "invalid backreference in 'comment' argument");
4949 goto error;
4950 }
4951 with_capture = 1;
4952 }
4953 p++;
4954 }
4955 if (with_capture && !inverse)
4956 memprintf(errmsg, "using backreference in a positive expect comment is useless");
4957 }
4958
4959 chk = calloc(1, sizeof(*chk));
4960 if (!chk) {
4961 memprintf(errmsg, "out of memory");
4962 goto error;
4963 }
4964 chk->action = TCPCHK_ACT_EXPECT;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004965 LIST_INIT(&chk->expect.onerror_fmt);
4966 LIST_INIT(&chk->expect.onsuccess_fmt);
4967 chk->comment = comment; comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004968 chk->expect.type = type;
4969 chk->expect.min_recv = min_recv;
Christopher Faulet12d57402020-04-10 09:58:42 +02004970 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
4971 chk->expect.flags |= (with_capture ? TCPCHK_EXPT_FL_CAP : 0);
Christopher Fauletec07e382020-04-07 14:56:26 +02004972 chk->expect.ok_status = ok_st;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02004973 chk->expect.err_status = err_st;
4974 chk->expect.tout_status = tout_st;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02004975 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004976
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02004977 if (on_success_msg) {
4978 px->conf.args.ctx = ARGC_SRV;
4979 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4980 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
4981 goto error;
4982 }
4983 free(on_success_msg);
4984 on_success_msg = NULL;
4985 }
4986 if (on_error_msg) {
4987 px->conf.args.ctx = ARGC_SRV;
4988 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4989 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
4990 goto error;
4991 }
4992 free(on_error_msg);
4993 on_error_msg = NULL;
4994 }
4995
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004996 switch (chk->expect.type) {
4997 case TCPCHK_EXPECT_STRING:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02004998 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
4999 if (!chk->expect.data.ptr) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005000 memprintf(errmsg, "out of memory");
5001 goto error;
5002 }
5003 break;
5004 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02005005 if (parse_binary(pattern, &chk->expect.data.ptr, (int *)&chk->expect.data.len, errmsg) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005006 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
5007 goto error;
5008 }
5009 case TCPCHK_EXPECT_REGEX:
5010 case TCPCHK_EXPECT_REGEX_BINARY:
5011 chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
5012 if (!chk->expect.regex)
5013 goto error;
5014 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02005015 case TCPCHK_EXPECT_CUSTOM:
5016 chk->expect.custom = NULL; /* Must be defined by the caller ! */
5017 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005018 case TCPCHK_EXPECT_UNDEF:
5019 free(chk);
5020 memprintf(errmsg, "pattern not found");
5021 goto error;
5022 }
5023
5024 /* All tcp-check expect points back to the first inverse expect rule in
5025 * a chain of one or more expect rule, potentially itself.
5026 */
5027 chk->expect.head = chk;
5028 list_for_each_entry_rev(prev_check, rules, list) {
5029 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet12d57402020-04-10 09:58:42 +02005030 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005031 chk->expect.head = prev_check;
5032 continue;
5033 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01005034 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005035 break;
5036 }
5037 return chk;
5038
5039 error:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005040 free_tcpcheck(chk, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005041 free(str);
5042 free(comment);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005043 free(on_success_msg);
5044 free(on_error_msg);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005045 release_sample_expr(status_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005046 return NULL;
5047}
5048
5049/* Parses the "tcp-check" proxy keyword */
5050static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5051 struct proxy *defpx, const char *file, int line,
5052 char **errmsg)
5053{
Christopher Faulet404f9192020-04-09 23:13:54 +02005054 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005055 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005056 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005057
5058 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5059 ret = 1;
5060
Christopher Faulet404f9192020-04-09 23:13:54 +02005061 /* Deduce the ruleset name from the proxy info */
5062 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5063 ((curpx == defpx) ? "defaults" : curpx->id),
5064 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005065
Christopher Faulet404f9192020-04-09 23:13:54 +02005066 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5067 if (rs == NULL) {
5068 rs = tcpcheck_ruleset_create(b_orig(&trash));
5069 if (rs == NULL) {
5070 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005071 goto error;
5072 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005073 }
5074
Gaetan Rivet5301b012020-02-25 17:19:17 +01005075 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005076 if (!LIST_ISEMPTY(&rs->rules)) {
5077 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005078 index = chk->index + 1;
5079 }
5080
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005081 cur_arg = 1;
5082 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005083 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005084 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005085 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005086 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005087 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005088 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005089 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005090 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005091 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5092
5093 if (!kw) {
5094 action_kw_tcp_check_build_list(&trash);
5095 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5096 "%s%s. but got '%s'",
5097 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5098 goto error;
5099 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005100 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005101 }
5102
5103 if (!chk) {
5104 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5105 goto error;
5106 }
5107 ret = (*errmsg != NULL); /* Handle warning */
5108
5109 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005110 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005111 LIST_ADDQ(&rs->rules, &chk->list);
5112
5113 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5114 !(curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK)) {
5115 /* Use this ruleset if the proxy already has tcp-check enabled */
5116 curpx->tcpcheck_rules.list = &rs->rules;
5117 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5118 }
5119 else {
5120 /* mark this ruleset as unused for now */
5121 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5122 }
5123
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005124 return ret;
5125
5126 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005127 free_tcpcheck(chk, 0);
5128 tcpcheck_ruleset_release(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005129 return -1;
5130}
5131
Christopher Faulet51b129f2020-04-09 15:54:18 +02005132/* Parses the "http-check" proxy keyword */
5133static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5134 struct proxy *defpx, const char *file, int line,
5135 char **errmsg)
5136{
5137 int cur_arg, ret = 0;
5138
5139 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5140 ret = 1;
5141
5142 cur_arg = 1;
5143 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5144 /* enable a graceful server shutdown on an HTTP 404 response */
5145 curpx->options |= PR_O_DISABLE404;
5146 if (too_many_args(1, args, errmsg, NULL))
5147 goto error;
5148 }
5149 else if (strcmp(args[cur_arg], "send-state") == 0) {
5150 /* enable emission of the apparent state of a server in HTTP checks */
5151 curpx->options2 |= PR_O2_CHK_SNDST;
5152 if (too_many_args(1, args, errmsg, NULL))
5153 goto error;
5154 }
5155 else if (strcmp(args[cur_arg], "send") == 0) {
5156 free(curpx->check_hdrs);
5157 free(curpx->check_body);
5158 curpx->check_hdrs = curpx->check_body = NULL;
5159 curpx->check_hdrs_len = curpx->check_body_len = 0;
5160
5161 cur_arg++;
5162 while (*(args[cur_arg])) {
5163 if (strcmp(args[cur_arg], "hdr") == 0) {
5164 int hdr_len;
5165 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2])) {
5166 memprintf(errmsg, "'%s %s' : %s expects a name and a value as parameter.",
5167 args[0], args[1], args[cur_arg]);
5168 goto error;
5169 }
5170
5171 cur_arg++;
5172 hdr_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 4;
5173 curpx->check_hdrs = my_realloc2(curpx->check_hdrs, curpx->check_hdrs_len+hdr_len+1);
5174 if (curpx->check_hdrs == NULL) {
5175 memprintf(errmsg, "out of memory.");
5176 goto error;
5177 }
5178 snprintf(curpx->check_hdrs + curpx->check_hdrs_len, hdr_len+1, "%s: %s\r\n", args[cur_arg], args[cur_arg+1]);
5179 curpx->check_hdrs_len += hdr_len;
5180
5181 cur_arg++;
5182 }
5183 else if (strcmp(args[cur_arg], "body") == 0) {
5184 if (!*(args[cur_arg+1])) {
5185 memprintf(errmsg, "'%s %s' : %s expects a string as parameter.",
5186 args[0], args[1], args[cur_arg]);
5187 goto error;
5188 }
5189 cur_arg++;
5190 free(curpx->check_body);
5191 curpx->check_body = strdup(args[cur_arg]);
5192 curpx->check_body_len = strlen(args[cur_arg]);
5193 if (curpx->check_body == NULL) {
5194 memprintf(errmsg, "out of memory.");
5195 goto error;
5196 }
5197 }
5198 else {
5199 memprintf(errmsg, "'%s %s' only supports 'hdr' and 'body', found '%s'.",
5200 args[0], args[1], args[cur_arg]);
5201 goto error;
5202 }
5203 cur_arg++;
5204 }
5205 }
5206 else if (strcmp(args[cur_arg], "expect") == 0) {
5207 const char *ptr_arg;
5208
5209 if (curpx->options2 & PR_O2_EXP_TYPE) {
5210 memprintf(errmsg, "'%s %s' already specified.", args[0], args[1]);
5211 goto error;
5212 }
5213
5214 cur_arg++;
5215
5216 /* consider exclamation marks, sole or at the beginning of a word */
5217 while (*(ptr_arg = args[cur_arg])) {
5218 while (*ptr_arg == '!') {
5219 curpx->options2 ^= PR_O2_EXP_INV;
5220 ptr_arg++;
5221 }
5222 if (*ptr_arg)
5223 break;
5224 cur_arg++;
5225 }
5226
5227 /* now ptr_arg points to the beginning of a word past any possible
5228 * exclamation mark, and cur_arg is the argument which holds this word.
5229 */
5230 if (strcmp(ptr_arg, "status") == 0) {
5231 if (!*(args[cur_arg+1])) {
5232 memprintf(errmsg, "'%s %s %s' expects <string> as an argument.",
5233 args[0], args[1], ptr_arg);
5234 goto error;
5235 }
5236 curpx->options2 |= PR_O2_EXP_STS;
5237 free(curpx->expect_str);
5238 curpx->expect_str = strdup(args[cur_arg+1]);
5239 }
5240 else if (strcmp(ptr_arg, "string") == 0) {
5241 if (!*(args[cur_arg+1])) {
5242 memprintf(errmsg, "'%s %s %s' expects <string> as an argument.",
5243 args[0], args[1], ptr_arg);
5244 goto error;
5245 }
5246 curpx->options2 |= PR_O2_EXP_STR;
5247 free(curpx->expect_str);
5248 curpx->expect_str = strdup(args[cur_arg+1]);
5249 }
5250 else if (strcmp(ptr_arg, "rstatus") == 0) {
5251 if (!*(args[cur_arg+1])) {
5252 memprintf(errmsg, "'%s %s %s' expects <regex> as an argument.",
5253 args[0], args[1], ptr_arg);
5254 goto error;
5255 }
5256 curpx->options2 |= PR_O2_EXP_RSTS;
5257 free(curpx->expect_str);
5258 regex_free(curpx->expect_regex);
5259 curpx->expect_str = strdup(args[cur_arg+1]);
5260 if (!(curpx->expect_regex = regex_comp(args[cur_arg+1], 1, 1, errmsg))) {
5261 memprintf(errmsg, "'%s %s %s' : regular expression '%s': %s.",
5262 args[0], args[1], ptr_arg, args[cur_arg+1], *errmsg);
5263 goto error;
5264 }
5265 }
5266 else if (strcmp(ptr_arg, "rstring") == 0) {
5267 if (!*(args[cur_arg+1])) {
5268 memprintf(errmsg, "'%s %s %s' expects <regex> as an argument.",
5269 args[0], args[1], ptr_arg);
5270 goto error;
5271 }
5272 curpx->options2 |= PR_O2_EXP_RSTR;
5273 free(curpx->expect_str);
5274 regex_free(curpx->expect_regex);
5275 curpx->expect_str = strdup(args[cur_arg+1]);
5276 if (!(curpx->expect_regex = regex_comp(args[cur_arg+1], 1, 1, errmsg))) {
5277 memprintf(errmsg, "'%s %s %s' : regular expression '%s': %s.",
5278 args[0], args[1], ptr_arg, args[cur_arg + 1], *errmsg);
5279 goto error;
5280 }
5281 }
5282 else {
5283 memprintf(errmsg, "'%s %s' only supports [!] 'status', 'string', 'rstatus', 'rstring', found '%s'.",
5284 args[0], args[1], ptr_arg);
5285 goto error;
5286 }
5287 }
5288 else {
5289 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send', 'send-state' and 'expect'. but got '%s'.",
5290 args[0], args[1]);
5291 goto error;
5292 }
5293
5294 ret = (*errmsg != NULL); /* Handle warning */
5295 return ret;
5296
5297 error:
5298 return -1;
5299}
5300
Christopher Faulete9111b62020-04-09 18:12:08 +02005301/* Parses the "external-check" proxy keyword */
5302static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5303 struct proxy *defpx, const char *file, int line,
5304 char **errmsg)
5305{
5306 int cur_arg, ret = 0;
5307
5308 cur_arg = 1;
5309 if (!*(args[cur_arg])) {
5310 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5311 goto error;
5312 }
5313
5314 if (strcmp(args[cur_arg], "command") == 0) {
5315 if (too_many_args(2, args, errmsg, NULL))
5316 goto error;
5317 if (!*(args[cur_arg+1])) {
5318 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5319 goto error;
5320 }
5321 free(curpx->check_command);
5322 curpx->check_command = strdup(args[cur_arg+1]);
5323 }
5324 else if (strcmp(args[cur_arg], "path") == 0) {
5325 if (too_many_args(2, args, errmsg, NULL))
5326 goto error;
5327 if (!*(args[cur_arg+1])) {
5328 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5329 goto error;
5330 }
5331 free(curpx->check_path);
5332 curpx->check_path = strdup(args[cur_arg+1]);
5333 }
5334 else {
5335 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5336 args[0], args[1]);
5337 goto error;
5338 }
5339
5340 ret = (*errmsg != NULL); /* Handle warning */
5341 return ret;
5342
5343error:
5344 return -1;
5345}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005346
Christopher Faulet430e4802020-04-09 15:28:16 +02005347/* Parses the "option tcp-check" proxy keyword */
5348int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5349 const char *file, int line)
5350{
Christopher Faulet404f9192020-04-09 23:13:54 +02005351 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005352 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5353 int err_code = 0;
5354
5355 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5356 err_code |= ERR_WARN;
5357
5358 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5359 goto out;
5360
Christopher Faulet404f9192020-04-09 23:13:54 +02005361 curpx->options2 &= ~PR_O2_CHK_ANY;
5362 curpx->options2 |= PR_O2_TCPCHK_CHK;
5363
5364 if (!(rules->flags & TCPCHK_RULES_PROTO_CHK)) {
5365 /* If a tcp-check rulesset is already set, do nothing */
5366 if (rules->list)
5367 goto out;
5368
5369 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5370 * get it.
5371 */
5372 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5373 goto curpx_ruleset;
5374
5375 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5376 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5377 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5378 if (rs)
5379 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005380 }
5381
Christopher Faulet404f9192020-04-09 23:13:54 +02005382 curpx_ruleset:
5383 /* Deduce the ruleset name from the proxy info */
5384 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5385 ((curpx == defpx) ? "defaults" : curpx->id),
5386 curpx->conf.file, curpx->conf.line);
5387
5388 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5389 if (rs == NULL) {
5390 rs = tcpcheck_ruleset_create(b_orig(&trash));
5391 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005392 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5393 goto error;
5394 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005395 }
5396
Christopher Faulet404f9192020-04-09 23:13:54 +02005397 ruleset_found:
5398 free_tcpcheck_vars(&rules->preset_vars);
5399 rules->list = NULL;
5400 rules->flags = 0;
5401
Christopher Faulet430e4802020-04-09 15:28:16 +02005402 free(curpx->check_req);
5403 curpx->check_req = NULL;
Christopher Faulet404f9192020-04-09 23:13:54 +02005404
5405 rules->list = &rs->rules;
Christopher Faulet430e4802020-04-09 15:28:16 +02005406
5407 out:
5408 return err_code;
5409
5410 error:
5411 err_code |= ERR_ALERT | ERR_FATAL;
5412 goto out;
5413}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005414
5415/* Parses the "option redis-check" proxy keyword */
5416int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5417 const char *file, int line)
5418{
5419 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5420 static char *redis_res = "+PONG\r\n";
5421
5422 struct tcpcheck_ruleset *rs = NULL;
5423 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5424 struct tcpcheck_rule *chk;
5425 char *errmsg = NULL;
5426 int err_code = 0;
5427
5428 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5429 err_code |= ERR_WARN;
5430
5431 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5432 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005433
5434 curpx->options2 &= ~PR_O2_CHK_ANY;
5435 curpx->options2 |= PR_O2_TCPCHK_CHK;
5436
5437 free_tcpcheck_vars(&rules->preset_vars);
5438 rules->list = NULL;
5439 rules->flags = 0;
5440
5441 rs = tcpcheck_ruleset_lookup("*redis-check");
5442 if (rs)
5443 goto ruleset_found;
5444
5445 rs = tcpcheck_ruleset_create("*redis-check");
5446 if (rs == NULL) {
5447 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5448 goto error;
5449 }
5450
5451 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5452 1, curpx, &rs->rules, file, line, &errmsg);
5453 if (!chk) {
5454 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5455 goto error;
5456 }
5457 chk->index = 0;
5458 LIST_ADDQ(&rs->rules, &chk->list);
5459
5460 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5461 "error-status", "L7STS",
5462 "on-error", "%[check.payload(),cut_crlf]",
5463 "on-success", "Redis server is ok",
5464 ""},
5465 1, curpx, &rs->rules, file, line, &errmsg);
5466 if (!chk) {
5467 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5468 goto error;
5469 }
5470 chk->index = 1;
5471 LIST_ADDQ(&rs->rules, &chk->list);
5472
5473 LIST_ADDQ(&tcpchecks_list, &rs->list);
5474
5475 ruleset_found:
5476 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005477 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005478
5479 out:
5480 free(errmsg);
5481 return err_code;
5482
5483 error:
5484 tcpcheck_ruleset_release(rs);
5485 err_code |= ERR_ALERT | ERR_FATAL;
5486 goto out;
5487}
5488
Christopher Faulet811f78c2020-04-01 11:10:27 +02005489
5490/* Parses the "option ssl-hello-chk" proxy keyword */
5491int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5492 const char *file, int line)
5493{
5494 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5495 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5496 *
5497 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5498 */
5499 static char sslv3_client_hello[] = {
5500 "16" /* ContentType : 0x16 = Hanshake */
5501 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5502 "0079" /* ContentLength : 0x79 bytes after this one */
5503 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5504 "000075" /* HandshakeLength : 0x75 bytes after this one */
5505 "0300" /* Hello Version : 0x0300 = v3 */
5506 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5507 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5508 "00" /* Session ID length : empty (no session ID) */
5509 "004E" /* Cipher Suite Length : 78 bytes after this one */
5510 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5511 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5512 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5513 "000D" "000E" "000F" "0010" /* various bit lengths, */
5514 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5515 "0015" "0016" "0017" "0018"
5516 "0019" "001A" "001B" "002F"
5517 "0030" "0031" "0032" "0033"
5518 "0034" "0035" "0036" "0037"
5519 "0038" "0039" "003A"
5520 "01" /* Compression Length : 0x01 = 1 byte for types */
5521 "00" /* Compression Type : 0x00 = NULL compression */
5522 };
5523
5524 struct tcpcheck_ruleset *rs = NULL;
5525 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5526 struct tcpcheck_rule *chk;
5527 char *errmsg = NULL;
5528 int err_code = 0;
5529
5530 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5531 err_code |= ERR_WARN;
5532
5533 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5534 goto out;
5535
Christopher Faulet811f78c2020-04-01 11:10:27 +02005536 curpx->options2 &= ~PR_O2_CHK_ANY;
5537 curpx->options2 |= PR_O2_TCPCHK_CHK;
5538
5539 free_tcpcheck_vars(&rules->preset_vars);
5540 rules->list = NULL;
5541 rules->flags = 0;
5542
5543 rs = tcpcheck_ruleset_lookup("*ssl-hello-check");
5544 if (rs)
5545 goto ruleset_found;
5546
5547 rs = tcpcheck_ruleset_create("*ssl-hello-check");
5548 if (rs == NULL) {
5549 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5550 goto error;
5551 }
5552
5553 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5554 1, curpx, &rs->rules, file, line, &errmsg);
5555 if (!chk) {
5556 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5557 goto error;
5558 }
5559 chk->index = 0;
5560 LIST_ADDQ(&rs->rules, &chk->list);
5561
5562 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005563 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005564 "error-status", "L6RSP", "tout-status", "L6TOUT",
5565 ""},
5566 1, curpx, &rs->rules, file, line, &errmsg);
5567 if (!chk) {
5568 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5569 goto error;
5570 }
5571 chk->index = 1;
5572 LIST_ADDQ(&rs->rules, &chk->list);
5573
5574 LIST_ADDQ(&tcpchecks_list, &rs->list);
5575
5576 ruleset_found:
5577 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005578 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005579
5580 out:
5581 free(errmsg);
5582 return err_code;
5583
5584 error:
5585 tcpcheck_ruleset_release(rs);
5586 err_code |= ERR_ALERT | ERR_FATAL;
5587 goto out;
5588}
5589
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005590/* Parses the "option smtpchk" proxy keyword */
5591int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5592 const char *file, int line)
5593{
5594 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5595
5596 struct tcpcheck_ruleset *rs = NULL;
5597 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5598 struct tcpcheck_rule *chk;
5599 struct tcpcheck_var *var = NULL;
5600 char *cmd = NULL, *errmsg = NULL;
5601 int err_code = 0;
5602
5603 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5604 err_code |= ERR_WARN;
5605
5606 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5607 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005608
5609 curpx->options2 &= ~PR_O2_CHK_ANY;
5610 curpx->options2 |= PR_O2_TCPCHK_CHK;
5611
5612 free_tcpcheck_vars(&rules->preset_vars);
5613 rules->list = NULL;
5614 rules->flags = 0;
5615
5616 cur_arg += 2;
5617 if (*args[cur_arg] && *args[cur_arg+1] &&
5618 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5619 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5620 if (cmd)
5621 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
5622 }
5623 else {
5624 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
5625 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
5626 cmd = strdup("HELO localhost");
5627 }
5628
5629 var = tcpcheck_var_create("check.smtp_cmd");
5630 if (cmd == NULL || var == NULL) {
5631 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5632 goto error;
5633 }
5634 var->data.type = SMP_T_STR;
5635 var->data.u.str.area = cmd;
5636 var->data.u.str.data = strlen(cmd);
5637 LIST_INIT(&var->list);
5638 LIST_ADDQ(&rules->preset_vars, &var->list);
5639 cmd = NULL;
5640 var = NULL;
5641
5642 rs = tcpcheck_ruleset_lookup("*smtp-check");
5643 if (rs)
5644 goto ruleset_found;
5645
5646 rs = tcpcheck_ruleset_create("*smtp-check");
5647 if (rs == NULL) {
5648 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5649 goto error;
5650 }
5651
5652 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5653 1, curpx, &rs->rules, file, line, &errmsg);
5654 if (!chk) {
5655 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5656 goto error;
5657 }
5658 chk->index = 0;
5659 LIST_ADDQ(&rs->rules, &chk->list);
5660
5661 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
5662 "min-recv", "4",
5663 "error-status", "L7RSP",
5664 "on-error", "%[check.payload(),cut_crlf]",
5665 ""},
5666 1, curpx, &rs->rules, file, line, &errmsg);
5667 if (!chk) {
5668 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5669 goto error;
5670 }
5671 chk->index = 1;
5672 LIST_ADDQ(&rs->rules, &chk->list);
5673
5674 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
5675 "min-recv", "4",
5676 "error-status", "L7STS",
5677 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5678 "status-code", "check.payload(0,3)",
5679 ""},
5680 1, curpx, &rs->rules, file, line, &errmsg);
5681 if (!chk) {
5682 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5683 goto error;
5684 }
5685 chk->index = 2;
5686 LIST_ADDQ(&rs->rules, &chk->list);
5687
5688 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
5689 1, curpx, &rs->rules, file, line, &errmsg);
5690 if (!chk) {
5691 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5692 goto error;
5693 }
5694 chk->index = 3;
5695 LIST_ADDQ(&rs->rules, &chk->list);
5696
5697 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
5698 "min-recv", "4",
5699 "error-status", "L7STS",
5700 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5701 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
5702 "status-code", "check.payload(0,3)",
5703 ""},
5704 1, curpx, &rs->rules, file, line, &errmsg);
5705 if (!chk) {
5706 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5707 goto error;
5708 }
5709 chk->index = 4;
5710 LIST_ADDQ(&rs->rules, &chk->list);
5711
5712 LIST_ADDQ(&tcpchecks_list, &rs->list);
5713
5714 ruleset_found:
5715 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005716 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005717
5718 out:
5719 free(errmsg);
5720 return err_code;
5721
5722 error:
5723 free(cmd);
5724 free(var);
5725 free_tcpcheck_vars(&rules->preset_vars);
5726 tcpcheck_ruleset_release(rs);
5727 err_code |= ERR_ALERT | ERR_FATAL;
5728 goto out;
5729}
Christopher Faulet811f78c2020-04-01 11:10:27 +02005730
Christopher Fauletce355072020-04-02 11:44:39 +02005731/* Parses the "option pgsql-check" proxy keyword */
5732int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5733 const char *file, int line)
5734{
5735 static char pgsql_req[] = {
5736 "%[var(check.plen),htonl,hex]" /* The packet length*/
5737 "00030000" /* the version 3.0 */
5738 "7573657200" /* "user" key */
5739 "%[var(check.username),hex]00" /* the username */
5740 "00"
5741 };
5742
5743 struct tcpcheck_ruleset *rs = NULL;
5744 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5745 struct tcpcheck_rule *chk;
5746 struct tcpcheck_var *var = NULL;
5747 char *user = NULL, *errmsg = NULL;
5748 size_t packetlen = 0;
5749 int err_code = 0;
5750
5751 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5752 err_code |= ERR_WARN;
5753
5754 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5755 goto out;
5756
Christopher Fauletce355072020-04-02 11:44:39 +02005757 curpx->options2 &= ~PR_O2_CHK_ANY;
5758 curpx->options2 |= PR_O2_TCPCHK_CHK;
5759
5760 free_tcpcheck_vars(&rules->preset_vars);
5761 rules->list = NULL;
5762 rules->flags = 0;
5763
5764 cur_arg += 2;
5765 if (!*args[cur_arg] || !*args[cur_arg+1]) {
5766 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
5767 file, line, args[0], args[1]);
5768 goto error;
5769 }
5770 if (strcmp(args[cur_arg], "user") == 0) {
5771 packetlen = 15 + strlen(args[cur_arg+1]);
5772 user = strdup(args[cur_arg+1]);
5773
5774 var = tcpcheck_var_create("check.username");
5775 if (user == NULL || var == NULL) {
5776 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5777 goto error;
5778 }
5779 var->data.type = SMP_T_STR;
5780 var->data.u.str.area = user;
5781 var->data.u.str.data = strlen(user);
5782 LIST_INIT(&var->list);
5783 LIST_ADDQ(&rules->preset_vars, &var->list);
5784 user = NULL;
5785 var = NULL;
5786
5787 var = tcpcheck_var_create("check.plen");
5788 if (var == NULL) {
5789 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5790 goto error;
5791 }
5792 var->data.type = SMP_T_SINT;
5793 var->data.u.sint = packetlen;
5794 LIST_INIT(&var->list);
5795 LIST_ADDQ(&rules->preset_vars, &var->list);
5796 var = NULL;
5797 }
5798 else {
5799 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
5800 file, line, args[0], args[1]);
5801 goto error;
5802 }
5803
5804 rs = tcpcheck_ruleset_lookup("*pgsql-check");
5805 if (rs)
5806 goto ruleset_found;
5807
5808 rs = tcpcheck_ruleset_create("*pgsql-check");
5809 if (rs == NULL) {
5810 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5811 goto error;
5812 }
5813
5814 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
5815 1, curpx, &rs->rules, file, line, &errmsg);
5816 if (!chk) {
5817 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5818 goto error;
5819 }
5820 chk->index = 0;
5821 LIST_ADDQ(&rs->rules, &chk->list);
5822
5823 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
5824 1, curpx, &rs->rules, file, line, &errmsg);
5825 if (!chk) {
5826 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5827 goto error;
5828 }
5829 chk->index = 1;
5830 LIST_ADDQ(&rs->rules, &chk->list);
5831
5832 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
5833 "min-recv", "5",
5834 "error-status", "L7RSP",
5835 "on-error", "%[check.payload(6,0)]",
5836 ""},
5837 1, curpx, &rs->rules, file, line, &errmsg);
5838 if (!chk) {
5839 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5840 goto error;
5841 }
5842 chk->index = 2;
5843 LIST_ADDQ(&rs->rules, &chk->list);
5844
5845 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
5846 "min-recv", "9",
5847 "error-status", "L7STS",
5848 "on-success", "PostgreSQL server is ok",
5849 "on-error", "PostgreSQL unknown error",
5850 ""},
5851 1, curpx, &rs->rules, file, line, &errmsg);
5852 if (!chk) {
5853 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5854 goto error;
5855 }
5856 chk->index = 3;
5857 LIST_ADDQ(&rs->rules, &chk->list);
5858
5859 LIST_ADDQ(&tcpchecks_list, &rs->list);
5860
5861 ruleset_found:
5862 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005863 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02005864
5865 out:
5866 free(errmsg);
5867 return err_code;
5868
5869 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02005870 free(user);
5871 free(var);
5872 free_tcpcheck_vars(&rules->preset_vars);
5873 tcpcheck_ruleset_release(rs);
5874 err_code |= ERR_ALERT | ERR_FATAL;
5875 goto out;
5876}
5877
5878
5879/* Parses the "option mysql-check" proxy keyword */
5880int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5881 const char *file, int line)
5882{
5883 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
5884 * const char mysql40_client_auth_pkt[] = {
5885 * "\x0e\x00\x00" // packet length
5886 * "\x01" // packet number
5887 * "\x00\x00" // client capabilities
5888 * "\x00\x00\x01" // max packet
5889 * "haproxy\x00" // username (null terminated string)
5890 * "\x00" // filler (always 0x00)
5891 * "\x01\x00\x00" // packet length
5892 * "\x00" // packet number
5893 * "\x01" // COM_QUIT command
5894 * };
5895 */
5896 static char mysql40_rsname[] = "*mysql40-check";
5897 static char mysql40_req[] = {
5898 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5899 "0080" /* client capabilities */
5900 "000001" /* max packet */
5901 "%[var(check.username),hex]00" /* the username */
5902 "00" /* filler (always 0x00) */
5903 "010000" /* packet length*/
5904 "00" /* sequence ID */
5905 "01" /* COM_QUIT command */
5906 };
5907
5908 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
5909 * const char mysql41_client_auth_pkt[] = {
5910 * "\x0e\x00\x00\" // packet length
5911 * "\x01" // packet number
5912 * "\x00\x00\x00\x00" // client capabilities
5913 * "\x00\x00\x00\x01" // max packet
5914 * "\x21" // character set (UTF-8)
5915 * char[23] // All zeroes
5916 * "haproxy\x00" // username (null terminated string)
5917 * "\x00" // filler (always 0x00)
5918 * "\x01\x00\x00" // packet length
5919 * "\x00" // packet number
5920 * "\x01" // COM_QUIT command
5921 * };
5922 */
5923 static char mysql41_rsname[] = "*mysql41-check";
5924 static char mysql41_req[] = {
5925 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
5926 "00820000" /* client capabilities */
5927 "00800001" /* max packet */
5928 "21" /* character set (UTF-8) */
5929 "000000000000000000000000" /* 23 bytes, al zeroes */
5930 "0000000000000000000000"
5931 "%[var(check.username),hex]00" /* the username */
5932 "00" /* filler (always 0x00) */
5933 "010000" /* packet length*/
5934 "00" /* sequence ID */
5935 "01" /* COM_QUIT command */
5936 };
5937
5938 struct tcpcheck_ruleset *rs = NULL;
5939 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5940 struct tcpcheck_rule *chk;
5941 struct tcpcheck_var *var = NULL;
5942 char *mysql_rsname = "*mysql-check";
5943 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
5944 int index = 0, err_code = 0;
5945
5946 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5947 err_code |= ERR_WARN;
5948
5949 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5950 goto out;
5951
Christopher Fauletf2b3be52020-04-02 18:07:37 +02005952 curpx->options2 &= ~PR_O2_CHK_ANY;
5953 curpx->options2 |= PR_O2_TCPCHK_CHK;
5954
5955 free_tcpcheck_vars(&rules->preset_vars);
5956 rules->list = NULL;
5957 rules->flags = 0;
5958
5959 cur_arg += 2;
5960 if (*args[cur_arg]) {
5961 char *user;
5962 int packetlen, userlen;
5963
5964 if (strcmp(args[cur_arg], "user") != 0) {
5965 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
5966 file, line, args[0], args[1], args[cur_arg]);
5967 goto error;
5968 }
5969
5970 if (*(args[cur_arg+1]) == 0) {
5971 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
5972 file, line, args[0], args[1], args[cur_arg]);
5973 goto error;
5974 }
5975
5976 hdr = calloc(4, sizeof(*hdr));
5977 user = strdup(args[cur_arg+1]);
5978 userlen = strlen(args[cur_arg+1]);
5979
5980 if (hdr == NULL || user == NULL) {
5981 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5982 goto error;
5983 }
5984
5985 if (*args[cur_arg+2]) {
5986 if (strcmp(args[cur_arg+2], "post-41") != 0) {
5987 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
5988 file, line, args[cur_arg], args[cur_arg+2]);
5989 goto error;
5990 }
5991 packetlen = userlen + 7 + 27;
5992 mysql_req = mysql41_req;
5993 mysql_rsname = mysql41_rsname;
5994 }
5995 else {
5996 packetlen = userlen + 7;
5997 mysql_req = mysql40_req;
5998 mysql_rsname = mysql40_rsname;
5999 }
6000
6001 hdr[0] = (unsigned char)(packetlen & 0xff);
6002 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6003 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6004 hdr[3] = 1;
6005
6006 var = tcpcheck_var_create("check.header");
6007 if (var == NULL) {
6008 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6009 goto error;
6010 }
6011 var->data.type = SMP_T_STR;
6012 var->data.u.str.area = hdr;
6013 var->data.u.str.data = 4;
6014 LIST_INIT(&var->list);
6015 LIST_ADDQ(&rules->preset_vars, &var->list);
6016 hdr = NULL;
6017 var = NULL;
6018
6019 var = tcpcheck_var_create("check.username");
6020 if (var == NULL) {
6021 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6022 goto error;
6023 }
6024 var->data.type = SMP_T_STR;
6025 var->data.u.str.area = user;
6026 var->data.u.str.data = strlen(user);
6027 LIST_INIT(&var->list);
6028 LIST_ADDQ(&rules->preset_vars, &var->list);
6029 user = NULL;
6030 var = NULL;
6031 }
6032
6033 rs = tcpcheck_ruleset_lookup(mysql_rsname);
6034 if (rs)
6035 goto ruleset_found;
6036
6037 rs = tcpcheck_ruleset_create(mysql_rsname);
6038 if (rs == NULL) {
6039 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6040 goto error;
6041 }
6042
6043 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6044 1, curpx, &rs->rules, file, line, &errmsg);
6045 if (!chk) {
6046 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6047 goto error;
6048 }
6049 chk->index = index++;
6050 LIST_ADDQ(&rs->rules, &chk->list);
6051
6052 if (mysql_req) {
6053 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6054 1, curpx, &rs->rules, file, line, &errmsg);
6055 if (!chk) {
6056 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6057 goto error;
6058 }
6059 chk->index = index++;
6060 LIST_ADDQ(&rs->rules, &chk->list);
6061 }
6062
6063 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6064 1, curpx, &rs->rules, file, line, &errmsg);
6065 if (!chk) {
6066 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6067 goto error;
6068 }
6069 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6070 chk->index = index++;
6071 LIST_ADDQ(&rs->rules, &chk->list);
6072
6073 if (mysql_req) {
6074 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6075 1, curpx, &rs->rules, file, line, &errmsg);
6076 if (!chk) {
6077 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6078 goto error;
6079 }
6080 chk->expect.custom = tcpcheck_mysql_expect_ok;
6081 chk->index = index++;
6082 LIST_ADDQ(&rs->rules, &chk->list);
6083 }
6084
6085 LIST_ADDQ(&tcpchecks_list, &rs->list);
6086
6087 ruleset_found:
6088 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006089 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006090
6091 out:
6092 free(errmsg);
6093 return err_code;
6094
6095 error:
6096 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006097 free(user);
6098 free(var);
6099 free_tcpcheck_vars(&rules->preset_vars);
6100 tcpcheck_ruleset_release(rs);
6101 err_code |= ERR_ALERT | ERR_FATAL;
6102 goto out;
6103}
6104
Christopher Faulet1997eca2020-04-03 23:13:50 +02006105int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6106 const char *file, int line)
6107{
6108 static char *ldap_req = "300C020101600702010304008000";
6109
6110 struct tcpcheck_ruleset *rs = NULL;
6111 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6112 struct tcpcheck_rule *chk;
6113 char *errmsg = NULL;
6114 int err_code = 0;
6115
6116 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6117 err_code |= ERR_WARN;
6118
6119 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6120 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006121
6122 curpx->options2 &= ~PR_O2_CHK_ANY;
6123 curpx->options2 |= PR_O2_TCPCHK_CHK;
6124
6125 free_tcpcheck_vars(&rules->preset_vars);
6126 rules->list = NULL;
6127 rules->flags = 0;
6128
6129 rs = tcpcheck_ruleset_lookup("*ldap-check");
6130 if (rs)
6131 goto ruleset_found;
6132
6133 rs = tcpcheck_ruleset_create("*ldap-check");
6134 if (rs == NULL) {
6135 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6136 goto error;
6137 }
6138
6139 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6140 1, curpx, &rs->rules, file, line, &errmsg);
6141 if (!chk) {
6142 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6143 goto error;
6144 }
6145 chk->index = 0;
6146 LIST_ADDQ(&rs->rules, &chk->list);
6147
6148 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6149 "min-recv", "14",
6150 "on-error", "Not LDAPv3 protocol",
6151 ""},
6152 1, curpx, &rs->rules, file, line, &errmsg);
6153 if (!chk) {
6154 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6155 goto error;
6156 }
6157 chk->index = 1;
6158 LIST_ADDQ(&rs->rules, &chk->list);
6159
6160 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6161 1, curpx, &rs->rules, file, line, &errmsg);
6162 if (!chk) {
6163 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6164 goto error;
6165 }
6166 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6167 chk->index = 2;
6168 LIST_ADDQ(&rs->rules, &chk->list);
6169
6170 LIST_ADDQ(&tcpchecks_list, &rs->list);
6171
6172 ruleset_found:
6173 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006174 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006175
6176 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006177 free(errmsg);
6178 return err_code;
6179
6180 error:
6181 tcpcheck_ruleset_release(rs);
6182 err_code |= ERR_ALERT | ERR_FATAL;
6183 goto out;
6184}
6185
6186int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6187 const char *file, int line)
6188{
6189 struct tcpcheck_ruleset *rs = NULL;
6190 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6191 struct tcpcheck_rule *chk;
6192 char *spop_req = NULL;
6193 char *errmsg = NULL;
6194 int spop_len = 0, err_code = 0;
6195
6196 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6197 err_code |= ERR_WARN;
6198
6199 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6200 goto out;
6201
Christopher Faulet267b01b2020-04-04 10:27:09 +02006202 curpx->options2 &= ~PR_O2_CHK_ANY;
6203 curpx->options2 |= PR_O2_TCPCHK_CHK;
6204
6205 free_tcpcheck_vars(&rules->preset_vars);
6206 rules->list = NULL;
6207 rules->flags = 0;
6208
6209
6210 rs = tcpcheck_ruleset_lookup("*spop-check");
6211 if (rs)
6212 goto ruleset_found;
6213
6214 rs = tcpcheck_ruleset_create("*spop-check");
6215 if (rs == NULL) {
6216 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6217 goto error;
6218 }
6219
6220 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6221 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6222 goto error;
6223 }
6224 chunk_reset(&trash);
6225 dump_binary(&trash, spop_req, spop_len);
6226 trash.area[trash.data] = '\0';
6227
6228 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6229 1, curpx, &rs->rules, file, line, &errmsg);
6230 if (!chk) {
6231 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6232 goto error;
6233 }
6234 chk->index = 0;
6235 LIST_ADDQ(&rs->rules, &chk->list);
6236
6237 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
6238 1, curpx, &rs->rules, file, line, &errmsg);
6239 if (!chk) {
6240 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6241 goto error;
6242 }
6243 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6244 chk->index = 1;
6245 LIST_ADDQ(&rs->rules, &chk->list);
6246
6247 LIST_ADDQ(&tcpchecks_list, &rs->list);
6248
6249 ruleset_found:
6250 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006251 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006252
6253 out:
6254 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006255 free(errmsg);
6256 return err_code;
6257
6258 error:
6259 tcpcheck_ruleset_release(rs);
6260 err_code |= ERR_ALERT | ERR_FATAL;
6261 goto out;
6262}
Christopher Fauletce355072020-04-02 11:44:39 +02006263
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006264int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6265 const char *file, int line)
6266{
6267 static const char *http_req = "OPTIONS / HTTP/1.0\r\n";
6268 int err_code = 0;
6269
6270 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6271 err_code |= ERR_WARN;
6272
6273 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6274 goto out;
6275
6276 /* use HTTP request to check servers' health */
6277 free(curpx->check_req);
6278 free(curpx->check_hdrs);
6279 free(curpx->check_body);
6280
6281 curpx->check_req = curpx->check_hdrs = curpx->check_body = NULL;
6282 curpx->check_len = curpx->check_hdrs_len = curpx->check_body_len = 0;
6283 curpx->options2 &= ~PR_O2_CHK_ANY;
6284 curpx->options2 |= PR_O2_HTTP_CHK;
6285
6286 cur_arg += 2;
6287 if (!*args[cur_arg]) { /* no argument */
6288 curpx->check_req = strdup(http_req); /* default request */
6289 curpx->check_len = strlen(http_req);
6290 }
6291 else if (!*args[cur_arg+1]) { /* one argument : URI */
6292 curpx->check_len = strlen(args[cur_arg]) + strlen("OPTIONS HTTP/1.0\r\n");
6293 curpx->check_req = malloc(curpx->check_len+1);
6294 curpx->check_len = snprintf(curpx->check_req, curpx->check_len+1,
6295 "OPTIONS %s HTTP/1.0\r\n", args[cur_arg]);
6296 }
6297 else if (!*args[cur_arg+2]) { /* two arguments : METHOD URI */
6298 curpx->check_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + strlen(" HTTP/1.0\r\n") + 1;
6299 curpx->check_req = malloc(curpx->check_len+1);
6300 curpx->check_len = snprintf(curpx->check_req, curpx->check_len+1,
6301 "%s %s HTTP/1.0\r\n", args[cur_arg], args[cur_arg+1]);
6302 }
6303 else { /* 3 arguments : METHOD URI HTTP_VER */
6304 char *hdrs = strstr(args[cur_arg+2], "\r\n");
6305 char *body = strstr(args[cur_arg+2], "\r\n\r\n");
6306
6307 if (hdrs || body) {
6308 ha_warning("parsing [%s:%d]: '%s %s' : hiding headers or body at the end of the version string is deprecated."
6309 " Please, consider to use 'http-check send' directive instead.\n",
6310 file, line, args[0], args[1]);
6311 err_code |= ERR_WARN;
6312 }
6313
6314 if (hdrs == body)
6315 hdrs = NULL;
6316 if (hdrs) {
6317 *hdrs = '\0';
6318 hdrs += 2;
6319 }
6320 if (body) {
6321 *body = '\0';
6322 body += 4;
6323 }
6324
6325 curpx->check_len = strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + strlen(args[cur_arg+2]) + 4;
6326 curpx->check_req = malloc(curpx->check_len+1);
6327 snprintf(curpx->check_req, curpx->check_len+1, "%s %s %s\r\n",
6328 args[cur_arg], args[cur_arg+1], args[cur_arg+2]);
6329 if (hdrs) {
6330 curpx->check_hdrs_len = strlen(hdrs) + 2;
6331 curpx->check_hdrs = malloc(curpx->check_hdrs_len+1);
6332 snprintf(curpx->check_hdrs, curpx->check_hdrs_len+1, "%s\r\n", hdrs);
6333 }
6334 if (body) {
6335 curpx->check_body_len = strlen(body);
6336 curpx->check_body = strdup(body);
6337 }
6338 }
6339 out:
6340 return err_code;
6341
6342 error:
6343 err_code |= ERR_ALERT | ERR_FATAL;
6344 goto out;
6345}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006346
Christopher Faulet6f557912020-04-09 15:58:50 +02006347int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6348 const char *file, int line)
6349{
6350 int err_code = 0;
6351
6352 free(curpx->check_req);
6353 curpx->check_req = NULL;
6354 curpx->options2 &= ~PR_O2_CHK_ANY;
6355 curpx->options2 |= PR_O2_EXT_CHK;
6356 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6357 goto out;
6358
6359 out:
6360 return err_code;
6361}
6362
Christopher Fauletce8111e2020-04-06 15:04:11 +02006363/* Parse the "addr" server keyword */
6364static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6365 char **errmsg)
6366{
6367 struct sockaddr_storage *sk;
6368 struct protocol *proto;
6369 int port1, port2, err_code = 0;
6370
6371
6372 if (!*args[*cur_arg+1]) {
6373 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6374 goto error;
6375 }
6376
6377 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6378 if (!sk) {
6379 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6380 goto error;
6381 }
6382
6383 proto = protocol_by_family(sk->ss_family);
6384 if (!proto || !proto->connect) {
6385 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6386 args[*cur_arg], args[*cur_arg+1]);
6387 goto error;
6388 }
6389
6390 if (port1 != port2) {
6391 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6392 args[*cur_arg], args[*cur_arg+1]);
6393 goto error;
6394 }
6395
6396 srv->check.addr = srv->agent.addr = *sk;
6397 srv->flags |= SRV_F_CHECKADDR;
6398 srv->flags |= SRV_F_AGENTADDR;
6399
6400 out:
6401 return err_code;
6402
6403 error:
6404 err_code |= ERR_ALERT | ERR_FATAL;
6405 goto out;
6406}
6407
6408
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006409/* Parse the "agent-addr" server keyword */
6410static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6411 char **errmsg)
6412{
6413 int err_code = 0;
6414
6415 if (!*(args[*cur_arg+1])) {
6416 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6417 goto error;
6418 }
6419 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6420 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6421 goto error;
6422 }
6423
6424 out:
6425 return err_code;
6426
6427 error:
6428 err_code |= ERR_ALERT | ERR_FATAL;
6429 goto out;
6430}
6431
6432/* Parse the "agent-check" server keyword */
6433static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6434 char **errmsg)
6435{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006436 struct tcpcheck_ruleset *rs = NULL;
6437 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6438 struct tcpcheck_rule *chk;
6439 int err_code = 0;
6440
6441 if (srv->do_agent)
6442 goto out;
6443
6444 if (!rules) {
6445 rules = calloc(1, sizeof(*rules));
6446 if (!rules) {
6447 memprintf(errmsg, "out of memory.");
6448 goto error;
6449 }
6450 LIST_INIT(&rules->preset_vars);
6451 srv->agent.tcpcheck_rules = rules;
6452 }
6453 rules->list = NULL;
6454 rules->flags = 0;
6455
6456 rs = tcpcheck_ruleset_lookup("*agent-check");
6457 if (rs)
6458 goto ruleset_found;
6459
6460 rs = tcpcheck_ruleset_create("*agent-check");
6461 if (rs == NULL) {
6462 memprintf(errmsg, "out of memory.");
6463 goto error;
6464 }
6465
6466 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6467 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6468 if (!chk) {
6469 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6470 goto error;
6471 }
6472 chk->index = 0;
6473 LIST_ADDQ(&rs->rules, &chk->list);
6474
6475 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
6476 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6477 if (!chk) {
6478 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6479 goto error;
6480 }
6481 chk->expect.custom = tcpcheck_agent_expect_reply;
6482 chk->index = 1;
6483 LIST_ADDQ(&rs->rules, &chk->list);
6484
6485 LIST_ADDQ(&tcpchecks_list, &rs->list);
6486
6487 ruleset_found:
6488 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006489 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006490 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006491
6492 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006493 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006494
6495 error:
6496 deinit_srv_agent_check(srv);
6497 tcpcheck_ruleset_release(rs);
6498 err_code |= ERR_ALERT | ERR_FATAL;
6499 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006500}
6501
6502/* Parse the "agent-inter" server keyword */
6503static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6504 char **errmsg)
6505{
6506 const char *err = NULL;
6507 unsigned int delay;
6508 int err_code = 0;
6509
6510 if (!*(args[*cur_arg+1])) {
6511 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6512 goto error;
6513 }
6514
6515 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6516 if (err == PARSE_TIME_OVER) {
6517 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6518 args[*cur_arg+1], args[*cur_arg], srv->id);
6519 goto error;
6520 }
6521 else if (err == PARSE_TIME_UNDER) {
6522 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6523 args[*cur_arg+1], args[*cur_arg], srv->id);
6524 goto error;
6525 }
6526 else if (err) {
6527 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6528 *err, srv->id);
6529 goto error;
6530 }
6531 if (delay <= 0) {
6532 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6533 delay, args[*cur_arg], srv->id);
6534 goto error;
6535 }
6536 srv->agent.inter = delay;
6537
6538 out:
6539 return err_code;
6540
6541 error:
6542 err_code |= ERR_ALERT | ERR_FATAL;
6543 goto out;
6544}
6545
6546/* Parse the "agent-port" server keyword */
6547static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6548 char **errmsg)
6549{
6550 int err_code = 0;
6551
6552 if (!*(args[*cur_arg+1])) {
6553 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6554 goto error;
6555 }
6556
6557 global.maxsock++;
6558 srv->agent.port = atol(args[*cur_arg+1]);
6559
6560 out:
6561 return err_code;
6562
6563 error:
6564 err_code |= ERR_ALERT | ERR_FATAL;
6565 goto out;
6566}
6567
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006568int set_srv_agent_send(struct server *srv, const char *send)
6569{
6570 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6571 struct tcpcheck_var *var = NULL;
6572 char *str;
6573
6574 str = strdup(send);
6575 var = tcpcheck_var_create("check.agent_string");
6576 if (str == NULL || var == NULL)
6577 goto error;
6578
6579 free_tcpcheck_vars(&rules->preset_vars);
6580
6581 var->data.type = SMP_T_STR;
6582 var->data.u.str.area = str;
6583 var->data.u.str.data = strlen(str);
6584 LIST_INIT(&var->list);
6585 LIST_ADDQ(&rules->preset_vars, &var->list);
6586
6587 return 1;
6588
6589 error:
6590 free(str);
6591 free(var);
6592 return 0;
6593}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006594
6595/* Parse the "agent-send" server keyword */
6596static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6597 char **errmsg)
6598{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006599 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006600 int err_code = 0;
6601
6602 if (!*(args[*cur_arg+1])) {
6603 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
6604 goto error;
6605 }
6606
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006607 if (!rules) {
6608 rules = calloc(1, sizeof(*rules));
6609 if (!rules) {
6610 memprintf(errmsg, "out of memory.");
6611 goto error;
6612 }
6613 LIST_INIT(&rules->preset_vars);
6614 srv->agent.tcpcheck_rules = rules;
6615 }
6616
6617 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006618 memprintf(errmsg, "out of memory.");
6619 goto error;
6620 }
6621
6622 out:
6623 return err_code;
6624
6625 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006626 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006627 err_code |= ERR_ALERT | ERR_FATAL;
6628 goto out;
6629}
6630
6631/* Parse the "no-agent-send" server keyword */
6632static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6633 char **errmsg)
6634{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006635 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006636 return 0;
6637}
6638
Christopher Fauletce8111e2020-04-06 15:04:11 +02006639/* Parse the "check" server keyword */
6640static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6641 char **errmsg)
6642{
6643 srv->do_check = 1;
6644 return 0;
6645}
6646
6647/* Parse the "check-send-proxy" server keyword */
6648static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6649 char **errmsg)
6650{
6651 srv->check.send_proxy = 1;
6652 return 0;
6653}
6654
6655/* Parse the "check-via-socks4" server keyword */
6656static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6657 char **errmsg)
6658{
6659 srv->check.via_socks4 = 1;
6660 return 0;
6661}
6662
6663/* Parse the "no-check" server keyword */
6664static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6665 char **errmsg)
6666{
6667 deinit_srv_check(srv);
6668 return 0;
6669}
6670
6671/* Parse the "no-check-send-proxy" server keyword */
6672static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6673 char **errmsg)
6674{
6675 srv->check.send_proxy = 0;
6676 return 0;
6677}
6678
6679/* Parse the "rise" server keyword */
6680static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6681 char **errmsg)
6682{
6683 int err_code = 0;
6684
6685 if (!*args[*cur_arg + 1]) {
6686 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6687 goto error;
6688 }
6689
6690 srv->check.rise = atol(args[*cur_arg+1]);
6691 if (srv->check.rise <= 0) {
6692 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6693 goto error;
6694 }
6695
6696 if (srv->check.health)
6697 srv->check.health = srv->check.rise;
6698
6699 out:
6700 return err_code;
6701
6702 error:
6703 deinit_srv_agent_check(srv);
6704 err_code |= ERR_ALERT | ERR_FATAL;
6705 goto out;
6706 return 0;
6707}
6708
6709/* Parse the "fall" server keyword */
6710static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6711 char **errmsg)
6712{
6713 int err_code = 0;
6714
6715 if (!*args[*cur_arg + 1]) {
6716 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
6717 goto error;
6718 }
6719
6720 srv->check.fall = atol(args[*cur_arg+1]);
6721 if (srv->check.fall <= 0) {
6722 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
6723 goto error;
6724 }
6725
6726 out:
6727 return err_code;
6728
6729 error:
6730 deinit_srv_agent_check(srv);
6731 err_code |= ERR_ALERT | ERR_FATAL;
6732 goto out;
6733 return 0;
6734}
6735
6736/* Parse the "inter" server keyword */
6737static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6738 char **errmsg)
6739{
6740 const char *err = NULL;
6741 unsigned int delay;
6742 int err_code = 0;
6743
6744 if (!*(args[*cur_arg+1])) {
6745 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6746 goto error;
6747 }
6748
6749 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6750 if (err == PARSE_TIME_OVER) {
6751 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6752 args[*cur_arg+1], args[*cur_arg], srv->id);
6753 goto error;
6754 }
6755 else if (err == PARSE_TIME_UNDER) {
6756 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6757 args[*cur_arg+1], args[*cur_arg], srv->id);
6758 goto error;
6759 }
6760 else if (err) {
6761 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6762 *err, srv->id);
6763 goto error;
6764 }
6765 if (delay <= 0) {
6766 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6767 delay, args[*cur_arg], srv->id);
6768 goto error;
6769 }
6770 srv->check.inter = delay;
6771
6772 out:
6773 return err_code;
6774
6775 error:
6776 err_code |= ERR_ALERT | ERR_FATAL;
6777 goto out;
6778}
6779
6780
6781/* Parse the "fastinter" server keyword */
6782static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6783 char **errmsg)
6784{
6785 const char *err = NULL;
6786 unsigned int delay;
6787 int err_code = 0;
6788
6789 if (!*(args[*cur_arg+1])) {
6790 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6791 goto error;
6792 }
6793
6794 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6795 if (err == PARSE_TIME_OVER) {
6796 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6797 args[*cur_arg+1], args[*cur_arg], srv->id);
6798 goto error;
6799 }
6800 else if (err == PARSE_TIME_UNDER) {
6801 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6802 args[*cur_arg+1], args[*cur_arg], srv->id);
6803 goto error;
6804 }
6805 else if (err) {
6806 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6807 *err, srv->id);
6808 goto error;
6809 }
6810 if (delay <= 0) {
6811 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6812 delay, args[*cur_arg], srv->id);
6813 goto error;
6814 }
6815 srv->check.fastinter = delay;
6816
6817 out:
6818 return err_code;
6819
6820 error:
6821 err_code |= ERR_ALERT | ERR_FATAL;
6822 goto out;
6823}
6824
6825
6826/* Parse the "downinter" server keyword */
6827static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6828 char **errmsg)
6829{
6830 const char *err = NULL;
6831 unsigned int delay;
6832 int err_code = 0;
6833
6834 if (!*(args[*cur_arg+1])) {
6835 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6836 goto error;
6837 }
6838
6839 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6840 if (err == PARSE_TIME_OVER) {
6841 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6842 args[*cur_arg+1], args[*cur_arg], srv->id);
6843 goto error;
6844 }
6845 else if (err == PARSE_TIME_UNDER) {
6846 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6847 args[*cur_arg+1], args[*cur_arg], srv->id);
6848 goto error;
6849 }
6850 else if (err) {
6851 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6852 *err, srv->id);
6853 goto error;
6854 }
6855 if (delay <= 0) {
6856 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
6857 delay, args[*cur_arg], srv->id);
6858 goto error;
6859 }
6860 srv->check.downinter = delay;
6861
6862 out:
6863 return err_code;
6864
6865 error:
6866 err_code |= ERR_ALERT | ERR_FATAL;
6867 goto out;
6868}
6869
6870/* Parse the "port" server keyword */
6871static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6872 char **errmsg)
6873{
6874 int err_code = 0;
6875
6876 if (!*(args[*cur_arg+1])) {
6877 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
6878 goto error;
6879 }
6880
6881 global.maxsock++;
6882 srv->check.port = atol(args[*cur_arg+1]);
6883 srv->flags |= SRV_F_CHECKPORT;
6884
6885 out:
6886 return err_code;
6887
6888 error:
6889 err_code |= ERR_ALERT | ERR_FATAL;
6890 goto out;
6891}
6892
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006893static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02006894 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
6895 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
6896 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006897 { 0, NULL, NULL },
6898}};
6899
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006900static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02006901 { "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 +02006902 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
6903 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
6904 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
6905 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
6906 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006907 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
6908 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
6909 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006910 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02006911 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
6912 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
6913 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
6914 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
6915 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
6916 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
6917 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
6918 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006919 { NULL, NULL, 0 },
6920}};
6921
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006922INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006923INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01006924
Willy Tarreaubd741542010-03-16 18:46:54 +01006925/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02006926 * Local variables:
6927 * c-indent-level: 8
6928 * c-basic-offset: 8
6929 * End:
6930 */