blob: 58bc88dc4cab4ae737446f20815cd13ee15dd487 [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>
Christopher Faulete5870d82020-04-15 11:32:03 +020041#include <common/http.h>
42#include <common/h1.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020043
44#include <types/global.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020045#include <types/dns.h>
William Lallemand9ed62032016-11-21 17:49:11 +010046#include <types/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020047
Gaetan Rivet707b52f2020-02-21 18:14:59 +010048#include <proto/action.h>
Christopher Fauletba3c68f2020-04-01 16:27:05 +020049#include <proto/arg.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020050#include <proto/backend.h>
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +020051#include <proto/checks.h>
William Lallemand9ed62032016-11-21 17:49:11 +010052#include <proto/stats.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020053#include <proto/fd.h>
54#include <proto/log.h>
Willy Tarreau53a47662017-08-28 10:53:00 +020055#include <proto/mux_pt.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020056#include <proto/queue.h>
Willy Tarreauc6f4ce82009-06-10 11:09:37 +020057#include <proto/port_range.h>
Willy Tarreaue8c66af2008-01-13 18:40:14 +010058#include <proto/proto_tcp.h>
Baptiste Assmann69e273f2013-12-11 00:52:19 +010059#include <proto/protocol.h>
Willy Tarreau2b5652f2006-12-31 17:46:05 +010060#include <proto/proxy.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020061#include <proto/server.h>
Willy Tarreau48d6bf22016-06-21 16:27:34 +020062#include <proto/signal.h>
Willy Tarreau9e000c62011-03-10 14:03:36 +010063#include <proto/stream_interface.h>
Willy Tarreaubaaee002006-06-26 02:48:02 +020064#include <proto/task.h>
Gaetan Rivet13a50432020-02-21 18:13:44 +010065#include <proto/vars.h>
Baptiste Assmanna68ca962015-04-14 01:15:08 +020066#include <proto/log.h>
67#include <proto/dns.h>
68#include <proto/proto_udp.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020069#include <proto/ssl_sock.h>
Christopher Fauletb7d30092020-03-30 15:19:03 +020070#include <proto/sample.h>
Olivier Houchard9130a962017-10-17 17:33:43 +020071
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +020072static int tcpcheck_get_step_id(struct check *, struct tcpcheck_rule *);
Willy Tarreau6bdcab02017-10-04 18:41:00 +020073static int tcpcheck_main(struct check *);
Olivier Houchard910b2bc2018-07-17 18:49:38 +020074static void __event_srv_chk_w(struct conn_stream *cs);
Olivier Houchard4501c3e2018-08-28 19:36:18 +020075static int wake_srv_chk(struct conn_stream *cs);
Olivier Houchardaf4021e2018-08-09 13:06:55 +020076static void __event_srv_chk_r(struct conn_stream *cs);
Willy Tarreaubd741542010-03-16 18:46:54 +010077
Christopher Faulet31c30fd2020-03-26 21:10:03 +010078static int srv_check_healthcheck_port(struct check *chk);
Christopher Faulete5870d82020-04-15 11:32:03 +020079static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px,
80 struct list *rules, const char *file, int line,
81 char **errmsg);
82static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
83 struct list *rules, unsigned int proto,
84 const char *file, int line, char **errmsg);
Christopher Faulet31c30fd2020-03-26 21:10:03 +010085
Christopher Faulet5d503fc2020-03-30 20:34:34 +020086/* Global list to share all tcp-checks */
87struct list tcpchecks_list = LIST_HEAD_INIT(tcpchecks_list);
88
89
Willy Tarreau8ceae722018-11-26 11:58:30 +010090DECLARE_STATIC_POOL(pool_head_email_alert, "email_alert", sizeof(struct email_alert));
91DECLARE_STATIC_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
Christopher Faulet31dff9b2017-10-23 15:45:20 +020092
Gaetan Rivet05d692d2020-02-14 17:42:54 +010093/* Dummy frontend used to create all checks sessions. */
94static struct proxy checks_fe;
Christopher Faulet31dff9b2017-10-23 15:45:20 +020095
Simon Horman63a4a822012-03-19 07:24:41 +090096static const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +010097 [HCHK_STATUS_UNKNOWN] = { CHK_RES_UNKNOWN, "UNK", "Unknown" },
98 [HCHK_STATUS_INI] = { CHK_RES_UNKNOWN, "INI", "Initializing" },
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +020099 [HCHK_STATUS_START] = { /* SPECIAL STATUS*/ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200100
Willy Tarreau23964182014-05-20 20:56:30 +0200101 /* Below we have finished checks */
102 [HCHK_STATUS_CHECKED] = { CHK_RES_NEUTRAL, "CHECKED", "No status change" },
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100103 [HCHK_STATUS_HANA] = { CHK_RES_FAILED, "HANA", "Health analyze" },
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100104
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100105 [HCHK_STATUS_SOCKERR] = { CHK_RES_FAILED, "SOCKERR", "Socket error" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200106
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100107 [HCHK_STATUS_L4OK] = { CHK_RES_PASSED, "L4OK", "Layer4 check passed" },
108 [HCHK_STATUS_L4TOUT] = { CHK_RES_FAILED, "L4TOUT", "Layer4 timeout" },
109 [HCHK_STATUS_L4CON] = { CHK_RES_FAILED, "L4CON", "Layer4 connection problem" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200110
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100111 [HCHK_STATUS_L6OK] = { CHK_RES_PASSED, "L6OK", "Layer6 check passed" },
112 [HCHK_STATUS_L6TOUT] = { CHK_RES_FAILED, "L6TOUT", "Layer6 timeout" },
113 [HCHK_STATUS_L6RSP] = { CHK_RES_FAILED, "L6RSP", "Layer6 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200114
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100115 [HCHK_STATUS_L7TOUT] = { CHK_RES_FAILED, "L7TOUT", "Layer7 timeout" },
116 [HCHK_STATUS_L7RSP] = { CHK_RES_FAILED, "L7RSP", "Layer7 invalid response" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200117
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200118 [HCHK_STATUS_L57DATA] = { /* DUMMY STATUS */ },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200119
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100120 [HCHK_STATUS_L7OKD] = { CHK_RES_PASSED, "L7OK", "Layer7 check passed" },
121 [HCHK_STATUS_L7OKCD] = { CHK_RES_CONDPASS, "L7OKC", "Layer7 check conditionally passed" },
122 [HCHK_STATUS_L7STS] = { CHK_RES_FAILED, "L7STS", "Layer7 wrong status" },
Simon Horman98637e52014-06-20 12:30:16 +0900123
124 [HCHK_STATUS_PROCERR] = { CHK_RES_FAILED, "PROCERR", "External check error" },
125 [HCHK_STATUS_PROCTOUT] = { CHK_RES_FAILED, "PROCTOUT", "External check timeout" },
Cyril Bonté77010d82014-08-07 01:55:37 +0200126 [HCHK_STATUS_PROCOK] = { CHK_RES_PASSED, "PROCOK", "External check passed" },
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200127};
128
Cyril Bontéac92a062014-12-27 22:28:38 +0100129const struct extcheck_env extcheck_envs[EXTCHK_SIZE] = {
130 [EXTCHK_PATH] = { "PATH", EXTCHK_SIZE_EVAL_INIT },
131 [EXTCHK_HAPROXY_PROXY_NAME] = { "HAPROXY_PROXY_NAME", EXTCHK_SIZE_EVAL_INIT },
132 [EXTCHK_HAPROXY_PROXY_ID] = { "HAPROXY_PROXY_ID", EXTCHK_SIZE_EVAL_INIT },
133 [EXTCHK_HAPROXY_PROXY_ADDR] = { "HAPROXY_PROXY_ADDR", EXTCHK_SIZE_EVAL_INIT },
134 [EXTCHK_HAPROXY_PROXY_PORT] = { "HAPROXY_PROXY_PORT", EXTCHK_SIZE_EVAL_INIT },
135 [EXTCHK_HAPROXY_SERVER_NAME] = { "HAPROXY_SERVER_NAME", EXTCHK_SIZE_EVAL_INIT },
136 [EXTCHK_HAPROXY_SERVER_ID] = { "HAPROXY_SERVER_ID", EXTCHK_SIZE_EVAL_INIT },
Christopher Fauletaaae9a02020-04-26 09:50:31 +0200137 [EXTCHK_HAPROXY_SERVER_ADDR] = { "HAPROXY_SERVER_ADDR", EXTCHK_SIZE_ADDR },
138 [EXTCHK_HAPROXY_SERVER_PORT] = { "HAPROXY_SERVER_PORT", EXTCHK_SIZE_UINT },
Cyril Bontéac92a062014-12-27 22:28:38 +0100139 [EXTCHK_HAPROXY_SERVER_MAXCONN] = { "HAPROXY_SERVER_MAXCONN", EXTCHK_SIZE_EVAL_INIT },
140 [EXTCHK_HAPROXY_SERVER_CURCONN] = { "HAPROXY_SERVER_CURCONN", EXTCHK_SIZE_ULONG },
141};
142
Simon Horman63a4a822012-03-19 07:24:41 +0900143static const struct analyze_status analyze_statuses[HANA_STATUS_SIZE] = { /* 0: ignore, 1: error, 2: OK */
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100144 [HANA_STATUS_UNKNOWN] = { "Unknown", { 0, 0 }},
145
146 [HANA_STATUS_L4_OK] = { "L4 successful connection", { 2, 0 }},
147 [HANA_STATUS_L4_ERR] = { "L4 unsuccessful connection", { 1, 1 }},
148
149 [HANA_STATUS_HTTP_OK] = { "Correct http response", { 0, 2 }},
150 [HANA_STATUS_HTTP_STS] = { "Wrong http response", { 0, 1 }},
151 [HANA_STATUS_HTTP_HDRRSP] = { "Invalid http response (headers)", { 0, 1 }},
152 [HANA_STATUS_HTTP_RSP] = { "Invalid http response", { 0, 1 }},
153
154 [HANA_STATUS_HTTP_READ_ERROR] = { "Read error (http)", { 0, 1 }},
155 [HANA_STATUS_HTTP_READ_TIMEOUT] = { "Read timeout (http)", { 0, 1 }},
156 [HANA_STATUS_HTTP_BROKEN_PIPE] = { "Close from server (http)", { 0, 1 }},
157};
158
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100159/* checks if <err> is a real error for errno or one that can be ignored, and
160 * return 0 for these ones or <err> for real ones.
161 */
162static inline int unclean_errno(int err)
163{
164 if (err == EAGAIN || err == EINPROGRESS ||
165 err == EISCONN || err == EALREADY)
166 return 0;
167 return err;
168}
169
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200170/*
171 * Convert check_status code to description
172 */
173const char *get_check_status_description(short check_status) {
174
175 const char *desc;
176
177 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200178 desc = check_statuses[check_status].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200179 else
180 desc = NULL;
181
182 if (desc && *desc)
183 return desc;
184 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200185 return check_statuses[HCHK_STATUS_UNKNOWN].desc;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200186}
187
188/*
189 * Convert check_status code to short info
190 */
191const char *get_check_status_info(short check_status) {
192
193 const char *info;
194
195 if (check_status < HCHK_STATUS_SIZE)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200196 info = check_statuses[check_status].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200197 else
198 info = NULL;
199
200 if (info && *info)
201 return info;
202 else
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200203 return check_statuses[HCHK_STATUS_UNKNOWN].info;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200204}
205
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100206const char *get_analyze_status(short analyze_status) {
207
208 const char *desc;
209
210 if (analyze_status < HANA_STATUS_SIZE)
211 desc = analyze_statuses[analyze_status].desc;
212 else
213 desc = NULL;
214
215 if (desc && *desc)
216 return desc;
217 else
218 return analyze_statuses[HANA_STATUS_UNKNOWN].desc;
219}
220
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200221/*
Simon Horman4a741432013-02-23 15:35:38 +0900222 * Set check->status, update check->duration and fill check->result with
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200223 * an adequate CHK_RES_* value. The new check->health is computed based
224 * on the result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200225 *
226 * Show information in logs about failed health check if server is UP
227 * or succeeded health checks if server is DOWN.
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200228 */
Simon Horman4a741432013-02-23 15:35:38 +0900229static void set_server_check_status(struct check *check, short status, const char *desc)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100230{
Simon Horman4a741432013-02-23 15:35:38 +0900231 struct server *s = check->server;
Willy Tarreaubef1b322014-05-13 21:01:39 +0200232 short prev_status = check->status;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200233 int report = 0;
Simon Horman4a741432013-02-23 15:35:38 +0900234
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200235 if (status == HCHK_STATUS_START) {
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100236 check->result = CHK_RES_UNKNOWN; /* no result yet */
Simon Horman4a741432013-02-23 15:35:38 +0900237 check->desc[0] = '\0';
238 check->start = now;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200239 return;
240 }
241
Simon Horman4a741432013-02-23 15:35:38 +0900242 if (!check->status)
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200243 return;
244
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200245 if (desc && *desc) {
Simon Horman4a741432013-02-23 15:35:38 +0900246 strncpy(check->desc, desc, HCHK_DESC_LEN-1);
247 check->desc[HCHK_DESC_LEN-1] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200248 } else
Simon Horman4a741432013-02-23 15:35:38 +0900249 check->desc[0] = '\0';
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200250
Simon Horman4a741432013-02-23 15:35:38 +0900251 check->status = status;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200252 if (check_statuses[status].result)
Simon Horman4a741432013-02-23 15:35:38 +0900253 check->result = check_statuses[status].result;
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200254
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100255 if (status == HCHK_STATUS_HANA)
Simon Horman4a741432013-02-23 15:35:38 +0900256 check->duration = -1;
257 else if (!tv_iszero(&check->start)) {
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200258 /* set_server_check_status() may be called more than once */
Simon Horman4a741432013-02-23 15:35:38 +0900259 check->duration = tv_ms_elapsed(&check->start, &now);
260 tv_zero(&check->start);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200261 }
262
Willy Tarreau23964182014-05-20 20:56:30 +0200263 /* no change is expected if no state change occurred */
264 if (check->result == CHK_RES_NEUTRAL)
265 return;
266
Olivier Houchard0923fa42019-01-11 18:43:04 +0100267 /* If the check was really just sending a mail, it won't have an
268 * associated server, so we're done now.
269 */
270 if (!s)
271 return;
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200272 report = 0;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200273
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200274 switch (check->result) {
275 case CHK_RES_FAILED:
Willy Tarreau12634e12014-05-23 11:32:36 +0200276 /* Failure to connect to the agent as a secondary check should not
277 * cause the server to be marked down.
278 */
279 if ((!(check->state & CHK_ST_AGENT) ||
Simon Hormaneaabd522015-02-26 11:26:17 +0900280 (check->status >= HCHK_STATUS_L57DATA)) &&
Christopher Fauletb119a792018-05-02 12:12:45 +0200281 (check->health > 0)) {
Olivier Houchard7059c552019-03-08 18:49:32 +0100282 _HA_ATOMIC_ADD(&s->counters.failed_checks, 1);
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200283 report = 1;
284 check->health--;
285 if (check->health < check->rise)
286 check->health = 0;
287 }
288 break;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200289
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200290 case CHK_RES_PASSED:
291 case CHK_RES_CONDPASS: /* "condpass" cannot make the first step but it OK after a "passed" */
292 if ((check->health < check->rise + check->fall - 1) &&
293 (check->result == CHK_RES_PASSED || check->health > 0)) {
294 report = 1;
295 check->health++;
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200296
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200297 if (check->health >= check->rise)
298 check->health = check->rise + check->fall - 1; /* OK now */
299 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200300
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200301 /* clear consecutive_errors if observing is enabled */
302 if (s->onerror)
303 s->consecutive_errors = 0;
304 break;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100305
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200306 default:
307 break;
308 }
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200309
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200310 if (s->proxy->options2 & PR_O2_LOGHCHKS &&
311 (status != prev_status || report)) {
312 chunk_printf(&trash,
Willy Tarreau12634e12014-05-23 11:32:36 +0200313 "%s check for %sserver %s/%s %s%s",
314 (check->state & CHK_ST_AGENT) ? "Agent" : "Health",
Willy Tarreauc93cd162014-05-13 15:54:22 +0200315 s->flags & SRV_F_BACKUP ? "backup " : "",
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100316 s->proxy->id, s->id,
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100317 (check->result == CHK_RES_CONDPASS) ? "conditionally ":"",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200318 (check->result >= CHK_RES_PASSED) ? "succeeded" : "failed");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200319
Emeric Brun5a133512017-10-19 14:42:30 +0200320 srv_append_status(&trash, s, check, -1, 0);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200321
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100322 chunk_appendf(&trash, ", status: %d/%d %s",
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200323 (check->health >= check->rise) ? check->health - check->rise + 1 : check->health,
324 (check->health >= check->rise) ? check->fall : check->rise,
325 (check->health >= check->rise) ? (s->uweight ? "UP" : "DRAIN") : "DOWN");
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200326
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200327 ha_warning("%s.\n", trash.area);
328 send_log(s->proxy, LOG_NOTICE, "%s.\n", trash.area);
329 send_email_alert(s, LOG_INFO, "%s", trash.area);
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200330 }
Krzysztof Piotr Oledzki09605412009-09-23 22:09:24 +0200331}
332
Willy Tarreau4eec5472014-05-20 22:32:27 +0200333/* Marks the check <check>'s server down if the current check is already failed
334 * and the server is not down yet nor in maintenance.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200335 */
Willy Tarreau4eec5472014-05-20 22:32:27 +0200336static void check_notify_failure(struct check *check)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200337{
Simon Horman4a741432013-02-23 15:35:38 +0900338 struct server *s = check->server;
Simon Hormane0d1bfb2011-06-21 14:34:58 +0900339
Willy Tarreau7b1d47c2014-05-20 14:55:13 +0200340 /* The agent secondary check should only cause a server to be marked
341 * as down if check->status is HCHK_STATUS_L7STS, which indicates
342 * that the agent returned "fail", "stopped" or "down".
343 * The implication here is that failure to connect to the agent
344 * as a secondary check should not cause the server to be marked
345 * down. */
346 if ((check->state & CHK_ST_AGENT) && check->status != HCHK_STATUS_L7STS)
347 return;
348
Willy Tarreau4eec5472014-05-20 22:32:27 +0200349 if (check->health > 0)
350 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100351
Willy Tarreau4eec5472014-05-20 22:32:27 +0200352 /* We only report a reason for the check if we did not do so previously */
Emeric Brun5a133512017-10-19 14:42:30 +0200353 srv_set_stopped(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200354}
355
Willy Tarreauaf549582014-05-16 17:37:50 +0200356/* Marks the check <check> as valid and tries to set its server up, provided
Willy Tarreau3e048382014-05-21 10:30:54 +0200357 * it isn't in maintenance, it is not tracking a down server and other checks
358 * comply. The rule is simple : by default, a server is up, unless any of the
359 * following conditions is true :
360 * - health check failed (check->health < rise)
361 * - agent check failed (agent->health < rise)
362 * - the server tracks a down server (track && track->state == STOPPED)
363 * Note that if the server has a slowstart, it will switch to STARTING instead
364 * of RUNNING. Also, only the health checks support the nolb mode, so the
365 * agent's success may not take the server out of this mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200366 */
Willy Tarreau3e048382014-05-21 10:30:54 +0200367static void check_notify_success(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200368{
Simon Horman4a741432013-02-23 15:35:38 +0900369 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100370
Emeric Brun52a91d32017-08-31 14:41:55 +0200371 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200372 return;
Cyril Bontécd19e512010-01-31 22:34:03 +0100373
Emeric Brun52a91d32017-08-31 14:41:55 +0200374 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreauaf549582014-05-16 17:37:50 +0200375 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100376
Willy Tarreau3e048382014-05-21 10:30:54 +0200377 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
378 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100379
Willy Tarreau3e048382014-05-21 10:30:54 +0200380 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
381 return;
Willy Tarreauaf549582014-05-16 17:37:50 +0200382
Emeric Brun52a91d32017-08-31 14:41:55 +0200383 if ((check->state & CHK_ST_AGENT) && s->next_state == SRV_ST_STOPPING)
Willy Tarreau3e048382014-05-21 10:30:54 +0200384 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100385
Emeric Brun5a133512017-10-19 14:42:30 +0200386 srv_set_running(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100387}
388
Willy Tarreaudb58b792014-05-21 13:57:23 +0200389/* Marks the check <check> as valid and tries to set its server into stopping mode
390 * if it was running or starting, and provided it isn't in maintenance and other
391 * checks comply. The conditions for the server to be marked in stopping mode are
392 * the same as for it to be turned up. Also, only the health checks support the
393 * nolb mode.
Willy Tarreauaf549582014-05-16 17:37:50 +0200394 */
Willy Tarreaudb58b792014-05-21 13:57:23 +0200395static void check_notify_stopping(struct check *check)
Willy Tarreauaf549582014-05-16 17:37:50 +0200396{
Simon Horman4a741432013-02-23 15:35:38 +0900397 struct server *s = check->server;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100398
Emeric Brun52a91d32017-08-31 14:41:55 +0200399 if (s->next_admin & SRV_ADMF_MAINT)
Willy Tarreauaf549582014-05-16 17:37:50 +0200400 return;
401
Willy Tarreaudb58b792014-05-21 13:57:23 +0200402 if (check->state & CHK_ST_AGENT)
403 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100404
Emeric Brun52a91d32017-08-31 14:41:55 +0200405 if (s->track && s->track->next_state == SRV_ST_STOPPED)
Willy Tarreaudb58b792014-05-21 13:57:23 +0200406 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100407
Willy Tarreaudb58b792014-05-21 13:57:23 +0200408 if ((s->check.state & CHK_ST_ENABLED) && (s->check.health < s->check.rise))
409 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100410
Willy Tarreaudb58b792014-05-21 13:57:23 +0200411 if ((s->agent.state & CHK_ST_ENABLED) && (s->agent.health < s->agent.rise))
412 return;
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100413
Willy Tarreaub26881a2017-12-23 11:16:49 +0100414 srv_set_stopping(s, NULL, (!s->track && !(s->proxy->options2 & PR_O2_LOGHCHKS)) ? check : NULL);
Krzysztof Piotr Oledzkic8b16fc2008-02-18 01:26:35 +0100415}
Willy Tarreaubaaee002006-06-26 02:48:02 +0200416
Willy Tarreau9fe7aae2013-12-31 23:47:37 +0100417/* note: use health_adjust() only, which first checks that the observe mode is
418 * enabled.
419 */
420void __health_adjust(struct server *s, short status)
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100421{
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100422 int failed;
423 int expire;
424
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100425 if (s->observe >= HANA_OBS_SIZE)
426 return;
427
Willy Tarreaubb956662013-01-24 00:37:39 +0100428 if (status >= HANA_STATUS_SIZE || !analyze_statuses[status].desc)
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100429 return;
430
431 switch (analyze_statuses[status].lr[s->observe - 1]) {
432 case 1:
433 failed = 1;
434 break;
435
436 case 2:
437 failed = 0;
438 break;
439
440 default:
441 return;
442 }
443
444 if (!failed) {
445 /* good: clear consecutive_errors */
446 s->consecutive_errors = 0;
447 return;
448 }
449
Olivier Houchard7059c552019-03-08 18:49:32 +0100450 _HA_ATOMIC_ADD(&s->consecutive_errors, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100451
452 if (s->consecutive_errors < s->consecutive_errors_limit)
453 return;
454
Willy Tarreau19d14ef2012-10-29 16:51:55 +0100455 chunk_printf(&trash, "Detected %d consecutive errors, last one was: %s",
456 s->consecutive_errors, get_analyze_status(status));
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100457
458 switch (s->onerror) {
459 case HANA_ONERR_FASTINTER:
460 /* force fastinter - nothing to do here as all modes force it */
461 break;
462
463 case HANA_ONERR_SUDDTH:
464 /* simulate a pre-fatal failed health check */
Simon Horman58c32972013-11-25 10:46:38 +0900465 if (s->check.health > s->check.rise)
466 s->check.health = s->check.rise + 1;
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100467
468 /* no break - fall through */
469
470 case HANA_ONERR_FAILCHK:
471 /* simulate a failed health check */
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200472 set_server_check_status(&s->check, HCHK_STATUS_HANA,
473 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200474 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100475 break;
476
477 case HANA_ONERR_MARKDWN:
478 /* mark server down */
Simon Horman58c32972013-11-25 10:46:38 +0900479 s->check.health = s->check.rise;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200480 set_server_check_status(&s->check, HCHK_STATUS_HANA,
481 trash.area);
Willy Tarreau4eec5472014-05-20 22:32:27 +0200482 check_notify_failure(&s->check);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100483 break;
484
485 default:
486 /* write a warning? */
487 break;
488 }
489
490 s->consecutive_errors = 0;
Olivier Houchard7059c552019-03-08 18:49:32 +0100491 _HA_ATOMIC_ADD(&s->counters.failed_hana, 1);
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100492
Simon Horman66183002013-02-23 10:16:43 +0900493 if (s->check.fastinter) {
494 expire = tick_add(now_ms, MS_TO_TICKS(s->check.fastinter));
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300495 if (s->check.task->expire > expire) {
Willy Tarreau5b3a2022012-09-28 15:01:02 +0200496 s->check.task->expire = expire;
Sergiy Prykhodko1d57e502013-09-21 12:05:00 +0300497 /* requeue check task with new expire */
498 task_queue(s->check.task);
499 }
Krzysztof Piotr Oledzki97f07b82009-12-15 22:31:24 +0100500 }
501}
502
Willy Tarreaua1dab552014-04-14 15:04:54 +0200503static int httpchk_build_status_header(struct server *s, char *buffer, int size)
Willy Tarreauef781042010-01-27 11:53:01 +0100504{
505 int sv_state;
506 int ratio;
507 int hlen = 0;
Joseph Lynch514061c2015-01-15 17:52:59 -0800508 char addr[46];
509 char port[6];
Willy Tarreauef781042010-01-27 11:53:01 +0100510 const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
511 "UP %d/%d", "UP",
512 "NOLB %d/%d", "NOLB",
513 "no check" };
514
515 memcpy(buffer + hlen, "X-Haproxy-Server-State: ", 24);
516 hlen += 24;
517
Willy Tarreauff5ae352013-12-11 20:36:34 +0100518 if (!(s->check.state & CHK_ST_ENABLED))
519 sv_state = 6;
Emeric Brun52a91d32017-08-31 14:41:55 +0200520 else if (s->cur_state != SRV_ST_STOPPED) {
Simon Horman58c32972013-11-25 10:46:38 +0900521 if (s->check.health == s->check.rise + s->check.fall - 1)
Willy Tarreauef781042010-01-27 11:53:01 +0100522 sv_state = 3; /* UP */
523 else
524 sv_state = 2; /* going down */
525
Emeric Brun52a91d32017-08-31 14:41:55 +0200526 if (s->cur_state == SRV_ST_STOPPING)
Willy Tarreauef781042010-01-27 11:53:01 +0100527 sv_state += 2;
528 } else {
Simon Horman125d0992013-02-24 17:23:38 +0900529 if (s->check.health)
Willy Tarreauef781042010-01-27 11:53:01 +0100530 sv_state = 1; /* going up */
531 else
532 sv_state = 0; /* DOWN */
533 }
534
Willy Tarreaua1dab552014-04-14 15:04:54 +0200535 hlen += snprintf(buffer + hlen, size - hlen,
Willy Tarreauef781042010-01-27 11:53:01 +0100536 srv_hlt_st[sv_state],
Emeric Brun52a91d32017-08-31 14:41:55 +0200537 (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
538 (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
Willy Tarreauef781042010-01-27 11:53:01 +0100539
Joseph Lynch514061c2015-01-15 17:52:59 -0800540 addr_to_str(&s->addr, addr, sizeof(addr));
Willy Tarreau04276f32017-01-06 17:41:29 +0100541 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
542 snprintf(port, sizeof(port), "%u", s->svc_port);
543 else
544 *port = 0;
Joseph Lynch514061c2015-01-15 17:52:59 -0800545
546 hlen += snprintf(buffer + hlen, size - hlen, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
547 addr, port, s->proxy->id, s->id,
Willy Tarreauef781042010-01-27 11:53:01 +0100548 global.node,
Emeric Brun52a91d32017-08-31 14:41:55 +0200549 (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
Willy Tarreauef781042010-01-27 11:53:01 +0100550 (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
551 s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
552 s->nbpend);
553
Emeric Brun52a91d32017-08-31 14:41:55 +0200554 if ((s->cur_state == SRV_ST_STARTING) &&
Willy Tarreauef781042010-01-27 11:53:01 +0100555 now.tv_sec < s->last_change + s->slowstart &&
556 now.tv_sec >= s->last_change) {
557 ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
Willy Tarreaua1dab552014-04-14 15:04:54 +0200558 hlen += snprintf(buffer + hlen, size - hlen, "; throttle=%d%%", ratio);
Willy Tarreauef781042010-01-27 11:53:01 +0100559 }
560
561 buffer[hlen++] = '\r';
562 buffer[hlen++] = '\n';
563
564 return hlen;
565}
566
Willy Tarreau20a18342013-12-05 00:31:46 +0100567/* Check the connection. If an error has already been reported or the socket is
568 * closed, keep errno intact as it is supposed to contain the valid error code.
569 * If no error is reported, check the socket's error queue using getsockopt().
570 * Warning, this must be done only once when returning from poll, and never
571 * after an I/O error was attempted, otherwise the error queue might contain
572 * inconsistent errors. If an error is detected, the CO_FL_ERROR is set on the
573 * socket. Returns non-zero if an error was reported, zero if everything is
574 * clean (including a properly closed socket).
575 */
576static int retrieve_errno_from_socket(struct connection *conn)
577{
578 int skerr;
579 socklen_t lskerr = sizeof(skerr);
580
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100581 if (conn->flags & CO_FL_ERROR && (unclean_errno(errno) || !conn->ctrl))
Willy Tarreau20a18342013-12-05 00:31:46 +0100582 return 1;
583
Willy Tarreau3c728722014-01-23 13:50:42 +0100584 if (!conn_ctrl_ready(conn))
Willy Tarreau20a18342013-12-05 00:31:46 +0100585 return 0;
586
Willy Tarreau585744b2017-08-24 14:31:19 +0200587 if (getsockopt(conn->handle.fd, SOL_SOCKET, SO_ERROR, &skerr, &lskerr) == 0)
Willy Tarreau20a18342013-12-05 00:31:46 +0100588 errno = skerr;
589
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100590 errno = unclean_errno(errno);
Willy Tarreau20a18342013-12-05 00:31:46 +0100591
592 if (!errno) {
593 /* we could not retrieve an error, that does not mean there is
594 * none. Just don't change anything and only report the prior
595 * error if any.
596 */
597 if (conn->flags & CO_FL_ERROR)
598 return 1;
599 else
600 return 0;
601 }
602
603 conn->flags |= CO_FL_ERROR | CO_FL_SOCK_WR_SH | CO_FL_SOCK_RD_SH;
604 return 1;
605}
606
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100607/* Try to collect as much information as possible on the connection status,
608 * and adjust the server status accordingly. It may make use of <errno_bck>
609 * if non-null when the caller is absolutely certain of its validity (eg:
610 * checked just after a syscall). If the caller doesn't have a valid errno,
611 * it can pass zero, and retrieve_errno_from_socket() will be called to try
612 * to extract errno from the socket. If no error is reported, it will consider
613 * the <expired> flag. This is intended to be used when a connection error was
614 * reported in conn->flags or when a timeout was reported in <expired>. The
615 * function takes care of not updating a server status which was already set.
616 * All situations where at least one of <expired> or CO_FL_ERROR are set
617 * produce a status.
618 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200619static void chk_report_conn_err(struct check *check, int errno_bck, int expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100620{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200621 struct conn_stream *cs = check->cs;
622 struct connection *conn = cs_conn(cs);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100623 const char *err_msg;
Willy Tarreau83061a82018-07-13 11:56:34 +0200624 struct buffer *chk;
Willy Tarreau213c6782014-10-02 14:51:02 +0200625 int step;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100626
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100627 if (check->result != CHK_RES_UNKNOWN)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100628 return;
629
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100630 errno = unclean_errno(errno_bck);
631 if (conn && errno)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100632 retrieve_errno_from_socket(conn);
633
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200634 if (conn && !(conn->flags & CO_FL_ERROR) &&
635 !(cs->flags & CS_FL_ERROR) && !expired)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100636 return;
637
638 /* we'll try to build a meaningful error message depending on the
639 * context of the error possibly present in conn->err_code, and the
640 * socket error possibly collected above. This is useful to know the
641 * exact step of the L6 layer (eg: SSL handshake).
642 */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200643 chk = get_trash_chunk();
644
Christopher Faulet799f3a42020-04-07 12:06:14 +0200645 if (check->type == PR_O2_TCPCHK_CHK &&
646 !(check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200647 step = tcpcheck_get_step_id(check, NULL);
Willy Tarreau213c6782014-10-02 14:51:02 +0200648 if (!step)
649 chunk_printf(chk, " at initial connection step of tcp-check");
650 else {
651 chunk_printf(chk, " at step %d of tcp-check", step);
652 /* we were looking for a string */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200653 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
654 if (check->current_step->connect.port)
655 chunk_appendf(chk, " (connect port %d)" ,check->current_step->connect.port);
Willy Tarreau213c6782014-10-02 14:51:02 +0200656 else
657 chunk_appendf(chk, " (connect)");
658 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200659 else if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT) {
660 struct tcpcheck_expect *expect = &check->current_step->expect;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100661
662 switch (expect->type) {
663 case TCPCHK_EXPECT_STRING:
Christopher Fauletf930e4c2020-04-10 09:20:02 +0200664 chunk_appendf(chk, " (expect string '%.*s')", (unsigned int)istlen(expect->data), expect->data.ptr);
Gaetan Rivetb616add2020-02-07 15:37:17 +0100665 break;
666 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +0200667 chunk_appendf(chk, " (expect binary '%.*s')", (unsigned int)istlen(expect->data), expect->data.ptr);
Gaetan Rivetb616add2020-02-07 15:37:17 +0100668 break;
669 case TCPCHK_EXPECT_REGEX:
Willy Tarreau213c6782014-10-02 14:51:02 +0200670 chunk_appendf(chk, " (expect regex)");
Gaetan Rivetb616add2020-02-07 15:37:17 +0100671 break;
Gaetan Rivetefab6c62020-02-07 15:37:17 +0100672 case TCPCHK_EXPECT_REGEX_BINARY:
673 chunk_appendf(chk, " (expect binary regex)");
674 break;
Christopher Faulete5870d82020-04-15 11:32:03 +0200675 case TCPCHK_EXPECT_HTTP_STATUS:
676 chunk_appendf(chk, " (expect HTTP status '%.*s')", (unsigned int)istlen(expect->data), expect->data.ptr);
677 break;
678 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
679 chunk_appendf(chk, " (expect HTTP status regex)");
680 break;
681 case TCPCHK_EXPECT_HTTP_BODY:
682 chunk_appendf(chk, " (expect HTTP body content '%.*s')", (unsigned int)istlen(expect->data), expect->data.ptr);
683 break;
684 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
685 chunk_appendf(chk, " (expect HTTP body regex)");
686 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +0200687 case TCPCHK_EXPECT_CUSTOM:
688 chunk_appendf(chk, " (expect custom function)");
689 break;
Gaetan Rivetb616add2020-02-07 15:37:17 +0100690 case TCPCHK_EXPECT_UNDEF:
691 chunk_appendf(chk, " (undefined expect!)");
692 break;
693 }
Willy Tarreau213c6782014-10-02 14:51:02 +0200694 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +0200695 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
Willy Tarreau213c6782014-10-02 14:51:02 +0200696 chunk_appendf(chk, " (send)");
697 }
Baptiste Assmann22b09d22015-05-01 08:03:04 +0200698
Christopher Faulet6f2a5e42020-04-01 13:11:41 +0200699 if (check->current_step && check->current_step->comment)
700 chunk_appendf(chk, " comment: '%s'", check->current_step->comment);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200701 }
702 }
703
Willy Tarreau00149122017-10-04 18:05:01 +0200704 if (conn && conn->err_code) {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100705 if (unclean_errno(errno))
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200706 chunk_printf(&trash, "%s (%s)%s", conn_err_code_str(conn), strerror(errno),
707 chk->area);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100708 else
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200709 chunk_printf(&trash, "%s%s", conn_err_code_str(conn),
710 chk->area);
711 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100712 }
713 else {
Willy Tarreauc8dc20a2019-12-27 12:03:27 +0100714 if (unclean_errno(errno)) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200715 chunk_printf(&trash, "%s%s", strerror(errno),
716 chk->area);
717 err_msg = trash.area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100718 }
719 else {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200720 err_msg = chk->area;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100721 }
722 }
723
Willy Tarreau00149122017-10-04 18:05:01 +0200724 if (check->state & CHK_ST_PORT_MISS) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +0200725 /* NOTE: this is reported after <fall> tries */
726 chunk_printf(chk, "No port available for the TCP connection");
727 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
728 }
729
Willy Tarreau00149122017-10-04 18:05:01 +0200730 if (!conn) {
731 /* connection allocation error before the connection was established */
732 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
733 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100734 else if (conn->flags & CO_FL_WAIT_L4_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100735 /* L4 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200736 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100737 set_server_check_status(check, HCHK_STATUS_L4CON, err_msg);
738 else if (expired)
739 set_server_check_status(check, HCHK_STATUS_L4TOUT, err_msg);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200740
741 /*
742 * might be due to a server IP change.
743 * Let's trigger a DNS resolution if none are currently running.
744 */
Olivier Houchard0923fa42019-01-11 18:43:04 +0100745 if (check->server)
746 dns_trigger_resolution(check->server->dns_requester);
Baptiste Assmanna68ca962015-04-14 01:15:08 +0200747
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100748 }
Willy Tarreauc192b0a2020-01-23 09:11:58 +0100749 else if (conn->flags & CO_FL_WAIT_L6_CONN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100750 /* L6 not established (yet) */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200751 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100752 set_server_check_status(check, HCHK_STATUS_L6RSP, err_msg);
753 else if (expired)
754 set_server_check_status(check, HCHK_STATUS_L6TOUT, err_msg);
755 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200756 else if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100757 /* I/O error after connection was established and before we could diagnose */
758 set_server_check_status(check, HCHK_STATUS_SOCKERR, err_msg);
759 }
760 else if (expired) {
Christopher Fauletcf80f2f2020-04-01 11:04:52 +0200761 enum healthcheck_status tout = HCHK_STATUS_L7TOUT;
762
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100763 /* connection established but expired check */
Christopher Faulet811f78c2020-04-01 11:10:27 +0200764 if (check->current_step && check->current_step->action == TCPCHK_ACT_EXPECT)
765 tout = check->current_step->expect.tout_status;
766 set_server_check_status(check, tout, err_msg);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100767 }
768
769 return;
770}
771
Olivier Houchard5c110b92018-08-14 17:04:58 +0200772/* This function checks if any I/O is wanted, and if so, attempts to do so */
773static struct task *event_srv_chk_io(struct task *t, void *ctx, unsigned short state)
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200774{
Olivier Houchard26e1a8f2018-09-12 15:15:12 +0200775 struct check *check = ctx;
776 struct conn_stream *cs = check->cs;
Olivier Houchard0923fa42019-01-11 18:43:04 +0100777 struct email_alertq *q = container_of(check, typeof(*q), check);
Olivier Houchardbc89ad82019-07-09 17:28:51 +0200778 int ret = 0;
Olivier Houchard4501c3e2018-08-28 19:36:18 +0200779
Willy Tarreau4f6516d2018-12-19 13:59:17 +0100780 if (!(check->wait_list.events & SUB_RETRY_SEND))
Olivier Houchardbc89ad82019-07-09 17:28:51 +0200781 ret = wake_srv_chk(cs);
782 if (ret == 0 && !(check->wait_list.events & SUB_RETRY_RECV)) {
Olivier Houchard0923fa42019-01-11 18:43:04 +0100783 if (check->server)
784 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
785 else
786 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200787 __event_srv_chk_r(cs);
Olivier Houchard0923fa42019-01-11 18:43:04 +0100788 if (check->server)
789 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
790 else
791 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200792 }
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200793 return NULL;
794}
795
796/* same as above but protected by the server lock.
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100797 *
798 * Please do NOT place any return statement in this function and only leave
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200799 * via the out label. NOTE THAT THIS FUNCTION DOESN'T LOCK, YOU PROBABLY WANT
800 * TO USE event_srv_chk_w() instead.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200801 */
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200802static void __event_srv_chk_w(struct conn_stream *cs)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200803{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200804 struct connection *conn = cs->conn;
805 struct check *check = cs->data;
Simon Horman4a741432013-02-23 15:35:38 +0900806 struct server *s = check->server;
Simon Horman4a741432013-02-23 15:35:38 +0900807 struct task *t = check->task;
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200808
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100809 if (unlikely(check->result == CHK_RES_FAILED))
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100810 goto out_wakeup;
811
Willy Tarreau20a18342013-12-05 00:31:46 +0100812 if (retrieve_errno_from_socket(conn)) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200813 chk_report_conn_err(check, errno, 0);
Willy Tarreau20a18342013-12-05 00:31:46 +0100814 goto out_wakeup;
815 }
Krzysztof Piotr Oledzki6492db52010-01-02 22:03:01 +0100816
Willy Tarreau06559ac2013-12-05 01:53:08 +0100817 /* here, we know that the connection is established. That's enough for
818 * a pure TCP check.
819 */
820 if (!check->type)
821 goto out_wakeup;
822
Willy Tarreauc09572f2017-10-04 11:58:22 +0200823 /* wake() will take care of calling tcpcheck_main() */
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100824 if (check->type == PR_O2_TCPCHK_CHK)
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200825 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +0200826
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200827 if (b_data(&check->bo)) {
Olivier Houcharded0f2072018-08-16 15:41:52 +0200828 cs->conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200829 b_realign_if_empty(&check->bo);
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200830 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200831 chk_report_conn_err(check, errno, 0);
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100832 goto out_wakeup;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200833 }
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200834 if (b_data(&check->bo)) {
Willy Tarreau4f6516d2018-12-19 13:59:17 +0100835 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200836 goto out;
837 }
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100838 }
Willy Tarreau6996e152007-04-30 14:37:43 +0200839
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100840 /* full request sent, we allow up to <timeout.check> if nonzero for a response */
841 if (s->proxy->timeout.check) {
842 t->expire = tick_add_ifset(now_ms, s->proxy->timeout.check);
843 task_queue(t);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200844 }
Olivier Houchard53216e72018-10-10 15:46:36 +0200845 goto out;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100846
Willy Tarreau83749182007-04-15 20:56:27 +0200847 out_wakeup:
Willy Tarreaufdccded2008-08-29 18:19:04 +0200848 task_wakeup(t, TASK_WOKEN_IO);
Olivier Houchard910b2bc2018-07-17 18:49:38 +0200849 out:
850 return;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200851}
852
Willy Tarreaubaaee002006-06-26 02:48:02 +0200853/*
Willy Tarreauf3c69202006-07-09 16:42:34 +0200854 * This function is used only for server health-checks. It handles the server's
Hervé COMMOWICK8776f1b2010-10-18 15:58:36 +0200855 * reply to an HTTP request, SSL HELLO or MySQL client Auth. It calls
Simon Horman4a741432013-02-23 15:35:38 +0900856 * set_server_check_status() to update check->status, check->duration
857 * and check->result.
Krzysztof Piotr Oledzki213014e2009-09-27 15:50:02 +0200858
859 * The set_server_check_status function is called with HCHK_STATUS_L7OKD if
860 * an HTTP server replies HTTP 2xx or 3xx (valid responses), if an SMTP server
861 * returns 2xx, HCHK_STATUS_L6OK if an SSL server returns at least 5 bytes in
862 * response to an SSL HELLO (the principle is that this is enough to
863 * distinguish between an SSL server and a pure TCP relay). All other cases will
864 * call it with a proper error status like HCHK_STATUS_L7STS, HCHK_STATUS_L6RSP,
865 * etc.
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100866 *
867 * Please do NOT place any return statement in this function and only leave
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200868 * via the out label.
869 *
870 * This must be called with the server lock held.
Willy Tarreaubaaee002006-06-26 02:48:02 +0200871 */
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200872static void __event_srv_chk_r(struct conn_stream *cs)
Willy Tarreaubaaee002006-06-26 02:48:02 +0200873{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200874 struct connection *conn = cs->conn;
875 struct check *check = cs->data;
Simon Horman4a741432013-02-23 15:35:38 +0900876 struct task *t = check->task;
Willy Tarreau03938182010-03-17 21:52:07 +0100877 int done;
Willy Tarreau83749182007-04-15 20:56:27 +0200878
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100879 if (unlikely(check->result == CHK_RES_FAILED))
Willy Tarreau83749182007-04-15 20:56:27 +0200880 goto out_wakeup;
Willy Tarreau83749182007-04-15 20:56:27 +0200881
Willy Tarreauc09572f2017-10-04 11:58:22 +0200882 /* wake() will take care of calling tcpcheck_main() */
Willy Tarreau62ac84f2017-11-05 10:11:13 +0100883 if (check->type == PR_O2_TCPCHK_CHK)
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200884 goto out;
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200885
Willy Tarreau83749182007-04-15 20:56:27 +0200886 /* Warning! Linux returns EAGAIN on SO_ERROR if data are still available
887 * but the connection was closed on the remote end. Fortunately, recv still
888 * works correctly and we don't need to do the getsockopt() on linux.
889 */
Nick Chalk57b1bf72010-03-16 15:50:46 +0000890
891 /* Set buffer to point to the end of the data already read, and check
892 * that there is free space remaining. If the buffer is full, proceed
893 * with running the checks without attempting another socket read.
894 */
Nick Chalk57b1bf72010-03-16 15:50:46 +0000895
Willy Tarreau03938182010-03-17 21:52:07 +0100896 done = 0;
Nick Chalk57b1bf72010-03-16 15:50:46 +0000897
Olivier Houchard511efea2018-08-16 15:30:32 +0200898 cs->conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0);
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200899 if (conn->flags & (CO_FL_ERROR | CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
Willy Tarreau03938182010-03-17 21:52:07 +0100900 done = 1;
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200901 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
Willy Tarreauf1503172012-09-28 19:39:36 +0200902 /* Report network errors only if we got no other data. Otherwise
903 * we'll let the upper layers decide whether the response is OK
904 * or not. It is very common that an RST sent by the server is
905 * reported as an error just after the last data chunk.
906 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200907 chk_report_conn_err(check, errno, 0);
Willy Tarreauc1a07962010-03-16 20:55:43 +0100908 goto out_wakeup;
909 }
Willy Tarreaubaaee002006-06-26 02:48:02 +0200910 }
911
Willy Tarreau0f0393f2019-09-24 10:43:03 +0200912 /* the rest of the code below expects the connection to be ready! */
Willy Tarreau911db9b2020-01-23 16:27:54 +0100913 if (conn->flags & CO_FL_WAIT_XPRT && !done)
Willy Tarreau0f0393f2019-09-24 10:43:03 +0200914 goto wait_more_data;
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100915
Willy Tarreau03938182010-03-17 21:52:07 +0100916 /* Intermediate or complete response received.
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200917 * Terminate string in b_head(&check->bi) buffer.
Willy Tarreau03938182010-03-17 21:52:07 +0100918 */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200919 if (b_data(&check->bi) < b_size(&check->bi))
920 b_head(&check->bi)[b_data(&check->bi)] = '\0';
Willy Tarreau03938182010-03-17 21:52:07 +0100921 else {
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200922 b_head(&check->bi)[b_data(&check->bi) - 1] = '\0';
Willy Tarreau03938182010-03-17 21:52:07 +0100923 done = 1; /* buffer full, don't wait for more data */
924 }
Krzysztof Piotr Oledzkif7089f52009-10-10 21:06:49 +0200925
Nick Chalk57b1bf72010-03-16 15:50:46 +0000926 /* Run the checks... */
Willy Tarreau1620ec32011-08-06 17:05:02 +0200927
Christopher Faulete5870d82020-04-15 11:32:03 +0200928 /* good connection is enough for pure TCP check */
929 if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
930 if (check->use_ssl == 1)
931 set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
932 else
933 set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
934 }
Willy Tarreau83749182007-04-15 20:56:27 +0200935
Willy Tarreauc7dd71a2007-11-30 08:33:21 +0100936 out_wakeup:
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100937 /* collect possible new errors */
Willy Tarreau4ff3b892017-10-16 15:17:17 +0200938 if (conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)
Willy Tarreaub5259bf2017-10-04 14:47:29 +0200939 chk_report_conn_err(check, 0, 0);
Willy Tarreaubaaee002006-06-26 02:48:02 +0200940
Nick Chalk57b1bf72010-03-16 15:50:46 +0000941 /* Reset the check buffer... */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200942 *b_head(&check->bi) = '\0';
943 b_reset(&check->bi);
Nick Chalk57b1bf72010-03-16 15:50:46 +0000944
Steven Davidovitz544d4812017-03-08 11:06:20 -0800945 /* Close the connection... We still attempt to nicely close if,
946 * for instance, SSL needs to send a "close notify." Later, we perform
947 * a hard close and reset the connection if some data are pending,
948 * otherwise we end up with many TIME_WAITs and eat all the source port
949 * range quickly. To avoid sending RSTs all the time, we first try to
950 * drain pending data.
Willy Tarreaufd29cc52012-11-23 09:18:20 +0100951 */
Olivier Houchard6c7e96a2019-07-02 16:35:18 +0200952 /* Call cs_shutr() first, to add the CO_FL_SOCK_RD_SH flag on the
953 * connection, to make sure cs_shutw() will not lead to a shutdown()
954 * that would provoke TIME_WAITs.
955 */
956 cs_shutr(cs, CS_SHR_DRAIN);
Willy Tarreauecdb3fe2017-10-05 15:25:48 +0200957 cs_shutw(cs, CS_SHW_NORMAL);
Willy Tarreau2b57cb82013-06-10 19:56:38 +0200958
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100959 /* OK, let's not stay here forever */
Willy Tarreau6aaa1b82013-12-11 17:09:34 +0100960 if (check->result == CHK_RES_FAILED)
Willy Tarreau25e2ab52013-12-04 11:17:05 +0100961 conn->flags |= CO_FL_ERROR;
962
Willy Tarreaufdccded2008-08-29 18:19:04 +0200963 task_wakeup(t, TASK_WOKEN_IO);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200964out:
Willy Tarreau3267d362012-08-17 23:53:56 +0200965 return;
Willy Tarreau03938182010-03-17 21:52:07 +0100966
967 wait_more_data:
Willy Tarreau4f6516d2018-12-19 13:59:17 +0100968 cs->conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Olivier Houchardaf4021e2018-08-09 13:06:55 +0200969 goto out;
Willy Tarreaubaaee002006-06-26 02:48:02 +0200970}
971
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200972/*
973 * This function is used only for server health-checks. It handles connection
974 * status updates including errors. If necessary, it wakes the check task up.
Willy Tarreau6bdcab02017-10-04 18:41:00 +0200975 * It returns 0 on normal cases, <0 if at least one close() has happened on the
976 * connection (eg: reconnect).
Willy Tarreaufb56aab2012-09-28 14:40:02 +0200977 */
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200978static int wake_srv_chk(struct conn_stream *cs)
Willy Tarreau20bea422012-07-06 12:00:49 +0200979{
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200980 struct connection *conn = cs->conn;
981 struct check *check = cs->data;
Olivier Houchard0923fa42019-01-11 18:43:04 +0100982 struct email_alertq *q = container_of(check, typeof(*q), check);
Willy Tarreau6bdcab02017-10-04 18:41:00 +0200983 int ret = 0;
Willy Tarreau20bea422012-07-06 12:00:49 +0200984
Olivier Houchard0923fa42019-01-11 18:43:04 +0100985 if (check->server)
986 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
987 else
988 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Emeric Brunf6ba17d2017-11-02 14:35:27 +0100989
Willy Tarreauc09572f2017-10-04 11:58:22 +0200990 /* we may have to make progress on the TCP checks */
Willy Tarreau6bdcab02017-10-04 18:41:00 +0200991 if (check->type == PR_O2_TCPCHK_CHK) {
992 ret = tcpcheck_main(check);
Olivier Houchard9aaf7782017-09-13 18:30:23 +0200993 cs = check->cs;
Willy Tarreau543abd42018-09-20 11:25:12 +0200994 conn = cs->conn;
Willy Tarreauc5940392019-09-05 17:38:40 +0200995 } else {
996 if (!(check->wait_list.events & SUB_RETRY_SEND))
997 __event_srv_chk_w(cs);
998 if (!(check->wait_list.events & SUB_RETRY_RECV))
999 __event_srv_chk_r(cs);
1000 }
Willy Tarreauc09572f2017-10-04 11:58:22 +02001001
Willy Tarreau4ff3b892017-10-16 15:17:17 +02001002 if (unlikely(conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR)) {
Willy Tarreau02b0f582013-12-03 15:42:33 +01001003 /* We may get error reports bypassing the I/O handlers, typically
1004 * the case when sending a pure TCP check which fails, then the I/O
1005 * handlers above are not called. This is completely handled by the
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001006 * main processing task so let's simply wake it up. If we get here,
1007 * we expect errno to still be valid.
1008 */
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001009 chk_report_conn_err(check, errno, 0);
Willy Tarreau2d351b62013-12-05 02:36:25 +01001010 task_wakeup(check->task, TASK_WOKEN_IO);
1011 }
Willy Tarreau911db9b2020-01-23 16:27:54 +01001012 else if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Willy Tarreau3be293f2014-02-05 18:31:24 +01001013 /* we may get here if only a connection probe was required : we
1014 * don't have any data to send nor anything expected in response,
1015 * so the completion of the connection establishment is enough.
1016 */
1017 task_wakeup(check->task, TASK_WOKEN_IO);
1018 }
Willy Tarreau2d351b62013-12-05 02:36:25 +01001019
Willy Tarreau6aaa1b82013-12-11 17:09:34 +01001020 if (check->result != CHK_RES_UNKNOWN) {
Christopher Faulet774c4862019-01-21 14:15:50 +01001021 /* Check complete or aborted. If connection not yet closed do it
1022 * now and wake the check task up to be sure the result is
1023 * handled ASAP. */
Willy Tarreaud85c4852015-03-13 00:40:28 +01001024 conn_sock_drain(conn);
Willy Tarreaua553ae92017-10-05 18:52:17 +02001025 cs_close(cs);
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001026 ret = -1;
Olivier Houchardf4949572019-07-02 17:42:22 +02001027 /* We may have been scheduled to run, and the
1028 * I/O handler expects to have a cs, so remove
1029 * the tasklet
1030 */
1031 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Christopher Faulet774c4862019-01-21 14:15:50 +01001032 task_wakeup(check->task, TASK_WOKEN_IO);
Willy Tarreau2d351b62013-12-05 02:36:25 +01001033 }
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001034
Olivier Houchard0923fa42019-01-11 18:43:04 +01001035 if (check->server)
1036 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
1037 else
1038 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Emeric Brunf6ba17d2017-11-02 14:35:27 +01001039
Willy Tarreau6bdcab02017-10-04 18:41:00 +02001040 /* if a connection got replaced, we must absolutely prevent the connection
1041 * handler from touching its fd, and perform the FD polling updates ourselves
1042 */
1043 if (ret < 0)
1044 conn_cond_update_polling(conn);
1045
1046 return ret;
Willy Tarreau20bea422012-07-06 12:00:49 +02001047}
1048
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001049struct data_cb check_conn_cb = {
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001050 .wake = wake_srv_chk,
Willy Tarreau8e0bb0a2016-11-24 16:58:12 +01001051 .name = "CHCK",
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001052};
1053
Willy Tarreaubaaee002006-06-26 02:48:02 +02001054/*
Willy Tarreau2e993902011-10-31 11:53:20 +01001055 * updates the server's weight during a warmup stage. Once the final weight is
1056 * reached, the task automatically stops. Note that any server status change
1057 * must have updated s->last_change accordingly.
1058 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001059static struct task *server_warmup(struct task *t, void *context, unsigned short state)
Willy Tarreau2e993902011-10-31 11:53:20 +01001060{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001061 struct server *s = context;
Willy Tarreau2e993902011-10-31 11:53:20 +01001062
1063 /* by default, plan on stopping the task */
1064 t->expire = TICK_ETERNITY;
Emeric Brun52a91d32017-08-31 14:41:55 +02001065 if ((s->next_admin & SRV_ADMF_MAINT) ||
1066 (s->next_state != SRV_ST_STARTING))
Willy Tarreau2e993902011-10-31 11:53:20 +01001067 return t;
1068
Willy Tarreau4fc49a92019-05-05 06:54:22 +02001069 HA_SPIN_LOCK(SERVER_LOCK, &s->lock);
1070
Willy Tarreau892337c2014-05-13 23:41:20 +02001071 /* recalculate the weights and update the state */
Willy Tarreau3ff577e2018-08-02 11:48:52 +02001072 server_recalc_eweight(s, 1);
Willy Tarreau2e993902011-10-31 11:53:20 +01001073
1074 /* probably that we can refill this server with a bit more connections */
Willy Tarreau4aac7db2014-05-16 11:48:10 +02001075 pendconn_grab_from_px(s);
Willy Tarreau2e993902011-10-31 11:53:20 +01001076
Willy Tarreau4fc49a92019-05-05 06:54:22 +02001077 HA_SPIN_UNLOCK(SERVER_LOCK, &s->lock);
1078
Willy Tarreau2e993902011-10-31 11:53:20 +01001079 /* get back there in 1 second or 1/20th of the slowstart interval,
1080 * whichever is greater, resulting in small 5% steps.
1081 */
Emeric Brun52a91d32017-08-31 14:41:55 +02001082 if (s->next_state == SRV_ST_STARTING)
Willy Tarreau2e993902011-10-31 11:53:20 +01001083 t->expire = tick_add(now_ms, MS_TO_TICKS(MAX(1000, s->slowstart / 20)));
1084 return t;
1085}
1086
Willy Tarreau894c6422017-10-04 15:58:52 +02001087/* returns the first NON-COMMENT tcp-check rule from list <list> or NULL if
1088 * none was found.
1089 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001090static struct tcpcheck_rule *get_first_tcpcheck_rule(struct tcpcheck_rules *rules)
Willy Tarreau894c6422017-10-04 15:58:52 +02001091{
1092 struct tcpcheck_rule *r;
1093
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001094 list_for_each_entry(r, rules->list, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01001095 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
Willy Tarreau894c6422017-10-04 15:58:52 +02001096 return r;
1097 }
1098 return NULL;
1099}
1100
Christopher Faulet95226db2020-04-15 11:34:04 +02001101/* returns the last NON-COMMENT tcp-check rule from list <list> or NULL if none
1102 * was found.
1103 */
1104static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
1105{
1106 struct tcpcheck_rule *r;
1107
1108 list_for_each_entry_rev(r, rules->list, list) {
1109 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
1110 return r;
1111 }
1112 return NULL;
1113}
1114
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001115/* returns the NON-COMMENT tcp-check rule from list <list> following <start> or
1116 * NULL if non was found. If <start> is NULL, it relies on
1117 * get_first_tcpcheck_rule().
1118 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001119static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001120{
1121 struct tcpcheck_rule *r;
1122
1123 if (!start)
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001124 return get_first_tcpcheck_rule(rules);
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001125
1126 r = LIST_NEXT(&start->list, typeof(r), list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02001127 list_for_each_entry_from(r, rules->list, list) {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01001128 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet1d22d7e2020-02-14 17:47:08 +01001129 return r;
1130 }
1131 return NULL;
1132}
1133
Willy Tarreau2e993902011-10-31 11:53:20 +01001134/*
Simon Horman98637e52014-06-20 12:30:16 +09001135 * establish a server health-check that makes use of a connection.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001136 *
1137 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001138 * - SF_ERR_NONE if everything's OK and tcpcheck_main() was not called
1139 * - SF_ERR_UP if if everything's OK and tcpcheck_main() was called
1140 * - SF_ERR_SRVTO if there are no more servers
1141 * - SF_ERR_SRVCL if the connection was refused by the server
1142 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1143 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1144 * - SF_ERR_INTERNAL for any other purely internal errors
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001145 * - 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 +01001146 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Hormanb00d17a2014-06-13 16:18:16 +09001147 * Note that we try to prevent the network stack from sending the ACK during the
1148 * connect() when a pure TCP check is used (without PROXY protocol).
1149 */
Simon Horman98637e52014-06-20 12:30:16 +09001150static int connect_conn_chk(struct task *t)
Simon Hormanb00d17a2014-06-13 16:18:16 +09001151{
1152 struct check *check = t->context;
1153 struct server *s = check->server;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001154 struct conn_stream *cs = check->cs;
1155 struct connection *conn = cs_conn(cs);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001156 struct protocol *proto;
1157 int ret;
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001158 int connflags = 0;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001159
Willy Tarreau00149122017-10-04 18:05:01 +02001160 /* we cannot have a connection here */
1161 if (conn)
1162 return SF_ERR_INTERNAL;
1163
Willy Tarreauf411cce2017-10-04 16:21:19 +02001164 /* for tcp-checks, the initial connection setup is handled separately as
1165 * it may be sent to a specific port and not to the server's.
1166 */
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001167 if (check->type == PR_O2_TCPCHK_CHK) {
1168 /* tcpcheck initialisation */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02001169 check->current_step = NULL;
Willy Tarreauf411cce2017-10-04 16:21:19 +02001170 tcpcheck_main(check);
1171 return SF_ERR_UP;
1172 }
1173
Simon Hormanb00d17a2014-06-13 16:18:16 +09001174 /* prepare a new connection */
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001175 cs = check->cs = cs_new(NULL);
1176 if (!check->cs)
Willy Tarreau00149122017-10-04 18:05:01 +02001177 return SF_ERR_RESOURCE;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001178 conn = cs->conn;
Olivier Houchard26e1a8f2018-09-12 15:15:12 +02001179 /* Maybe there were an older connection we were waiting on */
Willy Tarreau4f6516d2018-12-19 13:59:17 +01001180 check->wait_list.events = 0;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02001181 tasklet_set_tid(check->wait_list.tasklet, tid);
1182
Simon Hormanb00d17a2014-06-13 16:18:16 +09001183
Willy Tarreauca79f592019-07-17 19:04:47 +02001184 if (!sockaddr_alloc(&conn->dst))
1185 return SF_ERR_RESOURCE;
1186
Simon Horman41f58762015-01-30 11:22:56 +09001187 if (is_addr(&check->addr)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001188 /* we'll connect to the check addr specified on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001189 *conn->dst = check->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001190 }
1191 else {
1192 /* we'll connect to the addr on the server */
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001193 *conn->dst = s->addr;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001194 }
1195
Alexander Liu2a54bb72019-05-22 19:44:48 +08001196 if (s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1197 conn->send_proxy_ofs = 1;
1198 conn->flags |= CO_FL_SOCKS4;
1199 }
1200
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001201 proto = protocol_by_family(conn->dst->ss_family);
Olivier Houchard6377a002017-12-01 22:04:05 +01001202 conn->target = &s->obj_type;
1203
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001204 if ((conn->dst->ss_family == AF_INET) || (conn->dst->ss_family == AF_INET6)) {
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001205 int i = 0;
1206
1207 i = srv_check_healthcheck_port(check);
Olivier Houchard6377a002017-12-01 22:04:05 +01001208 if (i == 0)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001209 return SF_ERR_CHK_PORT;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001210
Willy Tarreaub3c81cb2019-07-17 16:54:52 +02001211 set_host_port(conn->dst, i);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001212 }
1213
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001214 /* no client address */
Thierry FOURNIERbb2ae642015-01-14 11:31:49 +01001215
Willy Tarreaube373152018-09-06 11:45:30 +02001216 conn_prepare(conn, proto, check->xprt);
Olivier Houchardf67be932019-01-29 15:47:43 +01001217 if (conn_install_mux(conn, &mux_pt_ops, cs, s->proxy, NULL) < 0)
1218 return SF_ERR_RESOURCE;
Willy Tarreaube373152018-09-06 11:45:30 +02001219 cs_attach(cs, check, &check_conn_cb);
1220
Christopher Fauleta202d1d2020-03-26 17:38:49 +01001221 /* only plain tcp check supports quick ACK */
1222 connflags |= (check->type ? CONNECT_HAS_DATA : CONNECT_DELACK_ALWAYS);
Simon Hormanb00d17a2014-06-13 16:18:16 +09001223
Willy Tarreaue7dff022015-04-03 01:14:29 +02001224 ret = SF_ERR_INTERNAL;
Olivier Houchardb68fda42017-08-04 18:39:01 +02001225 if (proto && proto->connect)
Olivier Houchardfdcb0072019-05-06 18:32:29 +02001226 ret = proto->connect(conn, connflags);
Willy Tarreau16257f62017-11-02 15:45:00 +01001227
Willy Tarreau16257f62017-11-02 15:45:00 +01001228
Olivier Houchard9130a962017-10-17 17:33:43 +02001229#ifdef USE_OPENSSL
Olivier Houcharda48437b2019-01-29 16:37:52 +01001230 if (ret == SF_ERR_NONE) {
1231 if (s->check.sni)
1232 ssl_sock_set_servername(conn, s->check.sni);
1233 if (s->check.alpn_str)
1234 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str,
1235 s->check.alpn_len);
1236 }
Olivier Houchard9130a962017-10-17 17:33:43 +02001237#endif
Willy Tarreauf4949772017-05-06 08:45:28 +02001238 if (s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
Simon Hormanb00d17a2014-06-13 16:18:16 +09001239 conn->send_proxy_ofs = 1;
1240 conn->flags |= CO_FL_SEND_PROXY;
Olivier Houchard37d78972019-12-30 15:13:42 +01001241 }
1242 if (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4) &&
1243 conn_ctrl_ready(conn)) {
Olivier Houchardfe50bfb2019-05-27 12:09:19 +02001244 if (xprt_add_hs(conn) < 0)
1245 ret = SF_ERR_RESOURCE;
Simon Hormanb00d17a2014-06-13 16:18:16 +09001246 }
1247
1248 return ret;
1249}
1250
Simon Horman98637e52014-06-20 12:30:16 +09001251static struct list pid_list = LIST_HEAD_INIT(pid_list);
Willy Tarreaubafbe012017-11-24 17:34:44 +01001252static struct pool_head *pool_head_pid_list;
Willy Tarreau86abe442018-11-25 20:12:18 +01001253__decl_spinlock(pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001254
1255void block_sigchld(void)
1256{
1257 sigset_t set;
1258 sigemptyset(&set);
1259 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001260 assert(ha_sigmask(SIG_BLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001261}
1262
1263void unblock_sigchld(void)
1264{
1265 sigset_t set;
1266 sigemptyset(&set);
Willy Tarreauebc92442016-06-21 17:29:46 +02001267 sigaddset(&set, SIGCHLD);
William Lallemand6e1796e2018-06-07 11:23:40 +02001268 assert(ha_sigmask(SIG_UNBLOCK, &set, NULL) == 0);
Simon Horman98637e52014-06-20 12:30:16 +09001269}
1270
Simon Horman98637e52014-06-20 12:30:16 +09001271static struct pid_list *pid_list_add(pid_t pid, struct task *t)
1272{
1273 struct pid_list *elem;
1274 struct check *check = t->context;
1275
Willy Tarreaubafbe012017-11-24 17:34:44 +01001276 elem = pool_alloc(pool_head_pid_list);
Simon Horman98637e52014-06-20 12:30:16 +09001277 if (!elem)
1278 return NULL;
1279 elem->pid = pid;
1280 elem->t = t;
1281 elem->exited = 0;
1282 check->curpid = elem;
1283 LIST_INIT(&elem->list);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001284
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001285 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001286 LIST_ADD(&pid_list, &elem->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001287 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001288
Simon Horman98637e52014-06-20 12:30:16 +09001289 return elem;
1290}
1291
Simon Horman98637e52014-06-20 12:30:16 +09001292static void pid_list_del(struct pid_list *elem)
1293{
1294 struct check *check;
1295
1296 if (!elem)
1297 return;
1298
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001299 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001300 LIST_DEL(&elem->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001301 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001302
Simon Horman98637e52014-06-20 12:30:16 +09001303 if (!elem->exited)
1304 kill(elem->pid, SIGTERM);
1305
1306 check = elem->t->context;
1307 check->curpid = NULL;
Willy Tarreaubafbe012017-11-24 17:34:44 +01001308 pool_free(pool_head_pid_list, elem);
Simon Horman98637e52014-06-20 12:30:16 +09001309}
1310
1311/* Called from inside SIGCHLD handler, SIGCHLD is blocked */
1312static void pid_list_expire(pid_t pid, int status)
1313{
1314 struct pid_list *elem;
1315
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001316 HA_SPIN_LOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001317 list_for_each_entry(elem, &pid_list, list) {
1318 if (elem->pid == pid) {
1319 elem->t->expire = now_ms;
1320 elem->status = status;
1321 elem->exited = 1;
Cyril Bonté9dbcfab2014-08-07 01:55:39 +02001322 task_wakeup(elem->t, TASK_WOKEN_IO);
Christopher Fauletcfda8472017-10-20 15:40:23 +02001323 break;
Simon Horman98637e52014-06-20 12:30:16 +09001324 }
1325 }
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001326 HA_SPIN_UNLOCK(PID_LIST_LOCK, &pid_list_lock);
Simon Horman98637e52014-06-20 12:30:16 +09001327}
1328
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001329static void sigchld_handler(struct sig_handler *sh)
Simon Horman98637e52014-06-20 12:30:16 +09001330{
1331 pid_t pid;
1332 int status;
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001333
Simon Horman98637e52014-06-20 12:30:16 +09001334 while ((pid = waitpid(0, &status, WNOHANG)) > 0)
1335 pid_list_expire(pid, status);
1336}
1337
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001338static int init_pid_list(void)
1339{
Willy Tarreaubafbe012017-11-24 17:34:44 +01001340 if (pool_head_pid_list != NULL)
Simon Horman98637e52014-06-20 12:30:16 +09001341 /* Nothing to do */
1342 return 0;
1343
Willy Tarreau48d6bf22016-06-21 16:27:34 +02001344 if (!signal_register_fct(SIGCHLD, sigchld_handler, SIGCHLD)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001345 ha_alert("Failed to set signal handler for external health checks: %s. Aborting.\n",
1346 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001347 return 1;
1348 }
1349
Willy Tarreaubafbe012017-11-24 17:34:44 +01001350 pool_head_pid_list = create_pool("pid_list", sizeof(struct pid_list), MEM_F_SHARED);
1351 if (pool_head_pid_list == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001352 ha_alert("Failed to allocate memory pool for external health checks: %s. Aborting.\n",
1353 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001354 return 1;
1355 }
1356
1357 return 0;
1358}
1359
Cyril Bontéac92a062014-12-27 22:28:38 +01001360/* helper macro to set an environment variable and jump to a specific label on failure. */
1361#define EXTCHK_SETENV(check, envidx, value, fail) { if (extchk_setenv(check, envidx, value)) goto fail; }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001362
1363/*
Cyril Bontéac92a062014-12-27 22:28:38 +01001364 * helper function to allocate enough memory to store an environment variable.
1365 * It will also check that the environment variable is updatable, and silently
1366 * fail if not.
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001367 */
Cyril Bontéac92a062014-12-27 22:28:38 +01001368static int extchk_setenv(struct check *check, int idx, const char *value)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001369{
1370 int len, ret;
Cyril Bontéac92a062014-12-27 22:28:38 +01001371 char *envname;
1372 int vmaxlen;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001373
Cyril Bontéac92a062014-12-27 22:28:38 +01001374 if (idx < 0 || idx >= EXTCHK_SIZE) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001375 ha_alert("Illegal environment variable index %d. Aborting.\n", idx);
Cyril Bontéac92a062014-12-27 22:28:38 +01001376 return 1;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001377 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001378
1379 envname = extcheck_envs[idx].name;
1380 vmaxlen = extcheck_envs[idx].vmaxlen;
1381
1382 /* Check if the environment variable is already set, and silently reject
1383 * the update if this one is not updatable. */
1384 if ((vmaxlen == EXTCHK_SIZE_EVAL_INIT) && (check->envp[idx]))
1385 return 0;
1386
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001387 /* Instead of sending NOT_USED, sending an empty value is preferable */
1388 if (strcmp(value, "NOT_USED") == 0) {
1389 value = "";
1390 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001391
1392 len = strlen(envname) + 1;
1393 if (vmaxlen == EXTCHK_SIZE_EVAL_INIT)
1394 len += strlen(value);
1395 else
1396 len += vmaxlen;
1397
1398 if (!check->envp[idx])
1399 check->envp[idx] = malloc(len + 1);
1400
1401 if (!check->envp[idx]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001402 ha_alert("Failed to allocate memory for the environment variable '%s'. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001403 return 1;
1404 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001405 ret = snprintf(check->envp[idx], len + 1, "%s=%s", envname, value);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001406 if (ret < 0) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001407 ha_alert("Failed to store the environment variable '%s'. Reason : %s. Aborting.\n", envname, strerror(errno));
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001408 return 1;
1409 }
Cyril Bontéac92a062014-12-27 22:28:38 +01001410 else if (ret > len) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001411 ha_alert("Environment variable '%s' was truncated. Aborting.\n", envname);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001412 return 1;
1413 }
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001414 return 0;
1415}
Simon Horman98637e52014-06-20 12:30:16 +09001416
1417static int prepare_external_check(struct check *check)
1418{
1419 struct server *s = check->server;
1420 struct proxy *px = s->proxy;
1421 struct listener *listener = NULL, *l;
1422 int i;
Simon Horman98637e52014-06-20 12:30:16 +09001423 const char *path = px->check_path ? px->check_path : DEF_CHECK_PATH;
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001424 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001425
1426 list_for_each_entry(l, &px->conf.listeners, by_fe)
1427 /* Use the first INET, INET6 or UNIX listener */
1428 if (l->addr.ss_family == AF_INET ||
1429 l->addr.ss_family == AF_INET6 ||
1430 l->addr.ss_family == AF_UNIX) {
1431 listener = l;
1432 break;
1433 }
1434
Simon Horman98637e52014-06-20 12:30:16 +09001435 check->curpid = NULL;
Cyril Bontéac92a062014-12-27 22:28:38 +01001436 check->envp = calloc((EXTCHK_SIZE + 1), sizeof(char *));
1437 if (!check->envp) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001438 ha_alert("Failed to allocate memory for environment variables. Aborting\n");
Cyril Bontéac92a062014-12-27 22:28:38 +01001439 goto err;
1440 }
Simon Horman98637e52014-06-20 12:30:16 +09001441
Cyril Bontéac92a062014-12-27 22:28:38 +01001442 check->argv = calloc(6, sizeof(char *));
1443 if (!check->argv) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001444 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001445 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001446 }
Simon Horman98637e52014-06-20 12:30:16 +09001447
1448 check->argv[0] = px->check_command;
1449
Cyril Bonté777be862014-12-02 21:21:35 +01001450 if (!listener) {
1451 check->argv[1] = strdup("NOT_USED");
1452 check->argv[2] = strdup("NOT_USED");
1453 }
1454 else if (listener->addr.ss_family == AF_INET ||
Simon Horman98637e52014-06-20 12:30:16 +09001455 listener->addr.ss_family == AF_INET6) {
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001456 addr_to_str(&listener->addr, buf, sizeof(buf));
1457 check->argv[1] = strdup(buf);
1458 port_to_str(&listener->addr, buf, sizeof(buf));
1459 check->argv[2] = strdup(buf);
Cyril Bonté777be862014-12-02 21:21:35 +01001460 }
1461 else if (listener->addr.ss_family == AF_UNIX) {
Simon Horman98637e52014-06-20 12:30:16 +09001462 const struct sockaddr_un *un;
1463
1464 un = (struct sockaddr_un *)&listener->addr;
1465 check->argv[1] = strdup(un->sun_path);
1466 check->argv[2] = strdup("NOT_USED");
Cyril Bonté777be862014-12-02 21:21:35 +01001467 }
1468 else {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001469 ha_alert("Starting [%s:%s] check: unsupported address family.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001470 goto err;
1471 }
1472
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001473 if (!check->argv[1] || !check->argv[2]) {
1474 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1475 goto err;
1476 }
1477
1478 check->argv[3] = calloc(EXTCHK_SIZE_ADDR, sizeof(*check->argv[3]));
1479 check->argv[4] = calloc(EXTCHK_SIZE_UINT, sizeof(*check->argv[4]));
1480 if (!check->argv[3] || !check->argv[4]) {
1481 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
1482 goto err;
1483 }
Willy Tarreau04276f32017-01-06 17:41:29 +01001484
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001485 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
Willy Tarreau04276f32017-01-06 17:41:29 +01001486 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001487 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
Simon Horman98637e52014-06-20 12:30:16 +09001488
Cyril Bontéac92a062014-12-27 22:28:38 +01001489 for (i = 0; i < 5; i++) {
1490 if (!check->argv[i]) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01001491 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Simon Horman98637e52014-06-20 12:30:16 +09001492 goto err;
Cyril Bontéac92a062014-12-27 22:28:38 +01001493 }
1494 }
Simon Horman98637e52014-06-20 12:30:16 +09001495
Cyril Bontéac92a062014-12-27 22:28:38 +01001496 EXTCHK_SETENV(check, EXTCHK_PATH, path, err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001497 /* Add proxy environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001498 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_NAME, px->id, err);
1499 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ID, ultoa_r(px->uuid, buf, sizeof(buf)), err);
1500 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_ADDR, check->argv[1], err);
1501 EXTCHK_SETENV(check, EXTCHK_HAPROXY_PROXY_PORT, check->argv[2], err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001502 /* Add server environment variables */
Cyril Bontéac92a062014-12-27 22:28:38 +01001503 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_NAME, s->id, err);
1504 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ID, ultoa_r(s->puid, buf, sizeof(buf)), err);
1505 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3], err);
1506 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4], err);
1507 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_MAXCONN, ultoa_r(s->maxconn, buf, sizeof(buf)), err);
1508 EXTCHK_SETENV(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)), err);
1509
1510 /* Ensure that we don't leave any hole in check->envp */
1511 for (i = 0; i < EXTCHK_SIZE; i++)
1512 if (!check->envp[i])
1513 EXTCHK_SETENV(check, i, "", err);
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001514
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001515 return 1;
Simon Horman98637e52014-06-20 12:30:16 +09001516err:
1517 if (check->envp) {
Cyril Bontéac92a062014-12-27 22:28:38 +01001518 for (i = 0; i < EXTCHK_SIZE; i++)
Cyril Bonté9ede66b2014-12-02 21:21:36 +01001519 free(check->envp[i]);
Simon Horman98637e52014-06-20 12:30:16 +09001520 free(check->envp);
1521 check->envp = NULL;
1522 }
1523
1524 if (check->argv) {
1525 for (i = 1; i < 5; i++)
1526 free(check->argv[i]);
1527 free(check->argv);
1528 check->argv = NULL;
1529 }
Cyril Bonté99c5bf52014-08-07 01:55:38 +02001530 return 0;
Simon Horman98637e52014-06-20 12:30:16 +09001531}
1532
Simon Hormanb00d17a2014-06-13 16:18:16 +09001533/*
Simon Horman98637e52014-06-20 12:30:16 +09001534 * establish a server health-check that makes use of a process.
1535 *
1536 * It can return one of :
Willy Tarreaue7dff022015-04-03 01:14:29 +02001537 * - SF_ERR_NONE if everything's OK
Willy Tarreaue7dff022015-04-03 01:14:29 +02001538 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
Tim Düsterhus4896c442016-11-29 02:15:19 +01001539 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
Simon Horman98637e52014-06-20 12:30:16 +09001540 *
1541 * Blocks and then unblocks SIGCHLD
1542 */
1543static int connect_proc_chk(struct task *t)
1544{
Cyril Bontéac92a062014-12-27 22:28:38 +01001545 char buf[256];
Simon Horman98637e52014-06-20 12:30:16 +09001546 struct check *check = t->context;
1547 struct server *s = check->server;
1548 struct proxy *px = s->proxy;
1549 int status;
1550 pid_t pid;
1551
Willy Tarreaue7dff022015-04-03 01:14:29 +02001552 status = SF_ERR_RESOURCE;
Simon Horman98637e52014-06-20 12:30:16 +09001553
1554 block_sigchld();
1555
1556 pid = fork();
1557 if (pid < 0) {
Willy Tarreaud96f1122019-12-03 07:07:36 +01001558 ha_alert("Failed to fork process for external health check%s: %s. Aborting.\n",
1559 (global.tune.options & GTUNE_INSECURE_FORK) ?
1560 "" : " (likely caused by missing 'insecure-fork-wanted')",
Christopher Faulet767a84b2017-11-24 16:50:31 +01001561 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001562 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1563 goto out;
1564 }
1565 if (pid == 0) {
1566 /* Child */
1567 extern char **environ;
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001568 struct rlimit limit;
Willy Tarreaub7b24782016-06-21 15:32:29 +02001569 int fd;
1570
1571 /* close all FDs. Keep stdin/stdout/stderr in verbose mode */
1572 fd = (global.mode & (MODE_QUIET|MODE_VERBOSE)) == MODE_QUIET ? 0 : 3;
1573
Willy Tarreau2555ccf2019-02-21 22:22:06 +01001574 my_closefrom(fd);
Willy Tarreaub7b24782016-06-21 15:32:29 +02001575
Willy Tarreau9f6dc722019-03-01 11:15:10 +01001576 /* restore the initial FD limits */
1577 limit.rlim_cur = rlim_fd_cur_at_boot;
1578 limit.rlim_max = rlim_fd_max_at_boot;
1579 if (setrlimit(RLIMIT_NOFILE, &limit) == -1) {
1580 getrlimit(RLIMIT_NOFILE, &limit);
1581 ha_warning("External check: failed to restore initial FD limits (cur=%u max=%u), using cur=%u max=%u\n",
1582 rlim_fd_cur_at_boot, rlim_fd_max_at_boot,
1583 (unsigned int)limit.rlim_cur, (unsigned int)limit.rlim_max);
1584 }
1585
Simon Horman98637e52014-06-20 12:30:16 +09001586 environ = check->envp;
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001587
1588 /* Update some environment variables and command args: curconn, server addr and server port */
Cyril Bontéac92a062014-12-27 22:28:38 +01001589 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_CURCONN, ultoa_r(s->cur_sess, buf, sizeof(buf)));
Christopher Fauletaaae9a02020-04-26 09:50:31 +02001590
1591 addr_to_str(&s->addr, check->argv[3], EXTCHK_SIZE_ADDR);
1592 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_ADDR, check->argv[3]);
1593
1594 *check->argv[4] = 0;
1595 if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
1596 snprintf(check->argv[4], EXTCHK_SIZE_UINT, "%u", s->svc_port);
1597 extchk_setenv(check, EXTCHK_HAPROXY_SERVER_PORT, check->argv[4]);
1598
Willy Tarreau2df8cad2019-07-01 07:51:29 +02001599 haproxy_unblock_signals();
Simon Horman98637e52014-06-20 12:30:16 +09001600 execvp(px->check_command, check->argv);
Christopher Faulet767a84b2017-11-24 16:50:31 +01001601 ha_alert("Failed to exec process for external health check: %s. Aborting.\n",
1602 strerror(errno));
Simon Horman98637e52014-06-20 12:30:16 +09001603 exit(-1);
1604 }
1605
1606 /* Parent */
1607 if (check->result == CHK_RES_UNKNOWN) {
1608 if (pid_list_add(pid, t) != NULL) {
1609 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1610
1611 if (px->timeout.check && px->timeout.connect) {
1612 int t_con = tick_add(now_ms, px->timeout.connect);
1613 t->expire = tick_first(t->expire, t_con);
1614 }
Willy Tarreaue7dff022015-04-03 01:14:29 +02001615 status = SF_ERR_NONE;
Simon Horman98637e52014-06-20 12:30:16 +09001616 goto out;
1617 }
1618 else {
1619 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1620 }
1621 kill(pid, SIGTERM); /* process creation error */
1622 }
1623 else
1624 set_server_check_status(check, HCHK_STATUS_SOCKERR, strerror(errno));
1625
1626out:
1627 unblock_sigchld();
1628 return status;
1629}
1630
1631/*
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001632 * manages a server health-check that uses an external process. Returns
Willy Tarreaubaaee002006-06-26 02:48:02 +02001633 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001634 *
1635 * Please do NOT place any return statement in this function and only leave
1636 * via the out_unlock label.
Willy Tarreaubaaee002006-06-26 02:48:02 +02001637 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001638static struct task *process_chk_proc(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09001639{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001640 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09001641 struct server *s = check->server;
Simon Horman98637e52014-06-20 12:30:16 +09001642 int rv;
1643 int ret;
1644 int expired = tick_is_expired(t->expire, now_ms);
1645
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001646 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09001647 if (!(check->state & CHK_ST_INPROGRESS)) {
1648 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001649 if (!expired) /* woke up too early */
1650 goto out_unlock;
Simon Horman98637e52014-06-20 12:30:16 +09001651
1652 /* we don't send any health-checks when the proxy is
1653 * stopped, the server should not be checked or the check
1654 * is disabled.
1655 */
1656 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
1657 s->proxy->state == PR_STSTOPPED)
1658 goto reschedule;
1659
1660 /* we'll initiate a new check */
1661 set_server_check_status(check, HCHK_STATUS_START, NULL);
1662
1663 check->state |= CHK_ST_INPROGRESS;
1664
Simon Hormandbf70192015-01-30 11:22:53 +09001665 ret = connect_proc_chk(t);
Willy Tarreaud7c3fbd2017-10-04 15:19:26 +02001666 if (ret == SF_ERR_NONE) {
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001667 /* the process was forked, we allow up to min(inter,
1668 * timeout.connect) for it to report its status, but
1669 * only when timeout.check is set as it may be to short
1670 * for a full check otherwise.
Simon Horman98637e52014-06-20 12:30:16 +09001671 */
1672 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1673
1674 if (s->proxy->timeout.check && s->proxy->timeout.connect) {
1675 int t_con = tick_add(now_ms, s->proxy->timeout.connect);
1676 t->expire = tick_first(t->expire, t_con);
1677 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02001678 task_set_affinity(t, tid_bit);
Simon Horman98637e52014-06-20 12:30:16 +09001679 goto reschedule;
Simon Horman98637e52014-06-20 12:30:16 +09001680 }
1681
Willy Tarreau1e62e2a2017-10-04 15:07:02 +02001682 /* here, we failed to start the check */
Simon Horman98637e52014-06-20 12:30:16 +09001683
1684 check->state &= ~CHK_ST_INPROGRESS;
1685 check_notify_failure(check);
1686
1687 /* we allow up to min(inter, timeout.connect) for a connection
1688 * to establish but only when timeout.check is set
1689 * as it may be to short for a full check otherwise
1690 */
1691 while (tick_is_expired(t->expire, now_ms)) {
1692 int t_con;
1693
1694 t_con = tick_add(t->expire, s->proxy->timeout.connect);
1695 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
1696
1697 if (s->proxy->timeout.check)
1698 t->expire = tick_first(t->expire, t_con);
1699 }
1700 }
1701 else {
1702 /* there was a test running.
1703 * First, let's check whether there was an uncaught error,
1704 * which can happen on connect timeout or error.
1705 */
1706 if (check->result == CHK_RES_UNKNOWN) {
1707 /* good connection is enough for pure TCP check */
1708 struct pid_list *elem = check->curpid;
1709 int status = HCHK_STATUS_UNKNOWN;
1710
1711 if (elem->exited) {
1712 status = elem->status; /* Save in case the process exits between use below */
1713 if (!WIFEXITED(status))
1714 check->code = -1;
1715 else
1716 check->code = WEXITSTATUS(status);
1717 if (!WIFEXITED(status) || WEXITSTATUS(status))
1718 status = HCHK_STATUS_PROCERR;
1719 else
1720 status = HCHK_STATUS_PROCOK;
1721 } else if (expired) {
1722 status = HCHK_STATUS_PROCTOUT;
Christopher Faulet767a84b2017-11-24 16:50:31 +01001723 ha_warning("kill %d\n", (int)elem->pid);
Simon Horman98637e52014-06-20 12:30:16 +09001724 kill(elem->pid, SIGTERM);
1725 }
1726 set_server_check_status(check, status, NULL);
1727 }
1728
1729 if (check->result == CHK_RES_FAILED) {
1730 /* a failure or timeout detected */
1731 check_notify_failure(check);
1732 }
1733 else if (check->result == CHK_RES_CONDPASS) {
1734 /* check is OK but asks for stopping mode */
1735 check_notify_stopping(check);
1736 }
1737 else if (check->result == CHK_RES_PASSED) {
1738 /* a success was detected */
1739 check_notify_success(check);
1740 }
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001741 task_set_affinity(t, 1);
Simon Horman98637e52014-06-20 12:30:16 +09001742 check->state &= ~CHK_ST_INPROGRESS;
1743
1744 pid_list_del(check->curpid);
1745
1746 rv = 0;
1747 if (global.spread_checks > 0) {
1748 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01001749 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Simon Horman98637e52014-06-20 12:30:16 +09001750 }
1751 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
1752 }
1753
1754 reschedule:
1755 while (tick_is_expired(t->expire, now_ms))
1756 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001757
1758 out_unlock:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01001759 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Simon Horman98637e52014-06-20 12:30:16 +09001760 return t;
1761}
1762
1763/*
1764 * manages a server health-check that uses a connection. Returns
1765 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001766 *
1767 * Please do NOT place any return statement in this function and only leave
1768 * via the out_unlock label.
Simon Horman98637e52014-06-20 12:30:16 +09001769 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001770static struct task *process_chk_conn(struct task *t, void *context, unsigned short state)
Willy Tarreaubaaee002006-06-26 02:48:02 +02001771{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001772 struct check *check = context;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001773 struct proxy *proxy = check->proxy;
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001774 struct conn_stream *cs = check->cs;
1775 struct connection *conn = cs_conn(cs);
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001776 int rv;
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001777 int ret;
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01001778 int expired = tick_is_expired(t->expire, now_ms);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001779
Olivier Houchard0923fa42019-01-11 18:43:04 +01001780 if (check->server)
1781 HA_SPIN_LOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau2c115e52013-12-11 19:41:16 +01001782 if (!(check->state & CHK_ST_INPROGRESS)) {
Willy Tarreau5a78f362012-11-23 12:47:05 +01001783 /* no check currently running */
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001784 if (!expired) /* woke up too early */
1785 goto out_unlock;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001786
Simon Horman671b6f02013-11-25 10:46:39 +09001787 /* we don't send any health-checks when the proxy is
1788 * stopped, the server should not be checked or the check
1789 * is disabled.
Willy Tarreaubaaee002006-06-26 02:48:02 +02001790 */
Willy Tarreau0d924cc2013-12-11 21:26:24 +01001791 if (((check->state & (CHK_ST_ENABLED | CHK_ST_PAUSED)) != CHK_ST_ENABLED) ||
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001792 proxy->state == PR_STSTOPPED)
Willy Tarreau5a78f362012-11-23 12:47:05 +01001793 goto reschedule;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001794
1795 /* we'll initiate a new check */
Simon Horman4a741432013-02-23 15:35:38 +09001796 set_server_check_status(check, HCHK_STATUS_START, NULL);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001797
Willy Tarreau2c115e52013-12-11 19:41:16 +01001798 check->state |= CHK_ST_INPROGRESS;
Willy Tarreauc9fa0482018-07-10 17:43:27 +02001799 b_reset(&check->bi);
1800 b_reset(&check->bo);
Willy Tarreau1ae1b7b2012-09-28 15:28:30 +02001801
Olivier Houchardaebeff72019-11-29 16:18:51 +01001802 task_set_affinity(t, tid_bit);
Simon Hormandbf70192015-01-30 11:22:53 +09001803 ret = connect_conn_chk(t);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001804 cs = check->cs;
1805 conn = cs_conn(cs);
Willy Tarreau00149122017-10-04 18:05:01 +02001806
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001807 switch (ret) {
Willy Tarreaue7dff022015-04-03 01:14:29 +02001808 case SF_ERR_UP:
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001809 goto out_unlock;
1810
Willy Tarreaue7dff022015-04-03 01:14:29 +02001811 case SF_ERR_NONE:
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001812 /* we allow up to min(inter, timeout.connect) for a connection
1813 * to establish but only when timeout.check is set
1814 * as it may be to short for a full check otherwise
1815 */
Simon Horman4a741432013-02-23 15:35:38 +09001816 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001817 if (proxy->timeout.check && proxy->timeout.connect) {
1818 int t_con = tick_add(now_ms, proxy->timeout.connect);
Willy Tarreaufb56aab2012-09-28 14:40:02 +02001819 t->expire = tick_first(t->expire, t_con);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001820 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01001821
Willy Tarreaucc705a62019-09-05 17:51:30 +02001822 if (check->type) {
1823 /* send the request if we have one. We avoid receiving
1824 * if not connected, unless we didn't subscribe for
1825 * sending since otherwise we won't be woken up.
1826 */
1827 __event_srv_chk_w(cs);
Willy Tarreau911db9b2020-01-23 16:27:54 +01001828 if (!(conn->flags & CO_FL_WAIT_XPRT) ||
Willy Tarreauc5940392019-09-05 17:38:40 +02001829 !(check->wait_list.events & SUB_RETRY_SEND))
1830 __event_srv_chk_r(cs);
Willy Tarreaucc705a62019-09-05 17:51:30 +02001831 }
Willy Tarreau06559ac2013-12-05 01:53:08 +01001832
Willy Tarreau5a78f362012-11-23 12:47:05 +01001833 goto reschedule;
1834
Willy Tarreaue7dff022015-04-03 01:14:29 +02001835 case SF_ERR_SRVTO: /* ETIMEDOUT */
1836 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
Willy Tarreau00149122017-10-04 18:05:01 +02001837 if (conn)
1838 conn->flags |= CO_FL_ERROR;
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001839 chk_report_conn_err(check, errno, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01001840 break;
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02001841 /* should share same code than cases below */
1842 case SF_ERR_CHK_PORT:
1843 check->state |= CHK_ST_PORT_MISS;
Willy Tarreaue7dff022015-04-03 01:14:29 +02001844 case SF_ERR_PRXCOND:
1845 case SF_ERR_RESOURCE:
1846 case SF_ERR_INTERNAL:
Willy Tarreau00149122017-10-04 18:05:01 +02001847 if (conn)
1848 conn->flags |= CO_FL_ERROR;
1849 chk_report_conn_err(check, conn ? 0 : ENOMEM, 0);
Willy Tarreau5a78f362012-11-23 12:47:05 +01001850 break;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001851 }
1852
Willy Tarreau5a78f362012-11-23 12:47:05 +01001853 /* here, we have seen a synchronous error, no fd was allocated */
Olivier Houchardaebeff72019-11-29 16:18:51 +01001854 task_set_affinity(t, MAX_THREADS_MASK);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001855 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02001856 if (check->wait_list.events)
1857 cs->conn->xprt->unsubscribe(cs->conn,
1858 cs->conn->xprt_ctx,
1859 check->wait_list.events,
1860 &check->wait_list);
1861 /* We may have been scheduled to run, and the
1862 * I/O handler expects to have a cs, so remove
1863 * the tasklet
1864 */
Willy Tarreau86eded62019-06-14 14:47:49 +02001865 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001866 cs_destroy(cs);
1867 cs = check->cs = NULL;
1868 conn = NULL;
Olivier Houchard390485a2017-10-24 19:03:30 +02001869 }
Willy Tarreau6b0a8502012-11-23 08:51:32 +01001870
Willy Tarreau2c115e52013-12-11 19:41:16 +01001871 check->state &= ~CHK_ST_INPROGRESS;
Willy Tarreau4eec5472014-05-20 22:32:27 +02001872 check_notify_failure(check);
Willy Tarreaubaaee002006-06-26 02:48:02 +02001873
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001874 /* we allow up to min(inter, timeout.connect) for a connection
1875 * to establish but only when timeout.check is set
1876 * as it may be to short for a full check otherwise
1877 */
Willy Tarreau0c303ee2008-07-07 00:09:58 +02001878 while (tick_is_expired(t->expire, now_ms)) {
1879 int t_con;
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001880
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001881 t_con = tick_add(t->expire, proxy->timeout.connect);
Simon Horman4a741432013-02-23 15:35:38 +09001882 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01001883 if (proxy->timeout.check)
Willy Tarreau0c303ee2008-07-07 00:09:58 +02001884 t->expire = tick_first(t->expire, t_con);
Krzysztof Piotr Oledzki5259dfe2008-01-21 01:54:06 +01001885 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02001886 }
1887 else {
Willy Tarreauf1503172012-09-28 19:39:36 +02001888 /* there was a test running.
1889 * First, let's check whether there was an uncaught error,
1890 * which can happen on connect timeout or error.
1891 */
Simon Hormanccaabcd2014-06-20 12:29:47 +09001892 if (check->result == CHK_RES_UNKNOWN) {
Willy Tarreau25e2ab52013-12-04 11:17:05 +01001893 /* good connection is enough for pure TCP check */
Willy Tarreau911db9b2020-01-23 16:27:54 +01001894 if (!(conn->flags & CO_FL_WAIT_XPRT) && !check->type) {
Christopher Fauletf61f33a2020-03-27 18:55:49 +01001895 if (check->use_ssl == 1)
Simon Horman4a741432013-02-23 15:35:38 +09001896 set_server_check_status(check, HCHK_STATUS_L6OK, NULL);
Willy Tarreauf1503172012-09-28 19:39:36 +02001897 else
Simon Horman4a741432013-02-23 15:35:38 +09001898 set_server_check_status(check, HCHK_STATUS_L4OK, NULL);
Willy Tarreauacbdc7a2012-11-23 14:02:10 +01001899 }
Willy Tarreau4ff3b892017-10-16 15:17:17 +02001900 else if ((conn->flags & CO_FL_ERROR) || cs->flags & CS_FL_ERROR || expired) {
Willy Tarreaub5259bf2017-10-04 14:47:29 +02001901 chk_report_conn_err(check, 0, expired);
Willy Tarreauf1503172012-09-28 19:39:36 +02001902 }
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01001903 else
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001904 goto out_unlock; /* timeout not reached, wait again */
Willy Tarreauf1503172012-09-28 19:39:36 +02001905 }
1906
Willy Tarreau74fa7fb2012-11-23 14:43:49 +01001907 /* check complete or aborted */
Gaetan Rivet05d692d2020-02-14 17:42:54 +01001908
1909 check->current_step = NULL;
1910 if (check->sess != NULL) {
1911 session_free(check->sess);
1912 check->sess = NULL;
1913 }
1914
Willy Tarreau00149122017-10-04 18:05:01 +02001915 if (conn && conn->xprt) {
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001916 /* The check was aborted and the connection was not yet closed.
1917 * This can happen upon timeout, or when an external event such
1918 * as a failed response coupled with "observe layer7" caused the
1919 * server state to be suddenly changed.
1920 */
Willy Tarreaud85c4852015-03-13 00:40:28 +01001921 conn_sock_drain(conn);
Willy Tarreaua553ae92017-10-05 18:52:17 +02001922 cs_close(cs);
Willy Tarreau5ba04f62013-02-12 15:23:12 +01001923 }
1924
Willy Tarreauac59f362017-10-08 11:10:19 +02001925 if (cs) {
Olivier Houchard49065542019-05-31 19:20:36 +02001926 if (check->wait_list.events)
1927 cs->conn->xprt->unsubscribe(cs->conn,
1928 cs->conn->xprt_ctx,
1929 check->wait_list.events,
1930 &check->wait_list);
1931 /* We may have been scheduled to run, and the
Willy Tarreau86eded62019-06-14 14:47:49 +02001932 * I/O handler expects to have a cs, so remove
1933 * the tasklet
1934 */
1935 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
Olivier Houchard9aaf7782017-09-13 18:30:23 +02001936 cs_destroy(cs);
1937 cs = check->cs = NULL;
1938 conn = NULL;
Willy Tarreau00149122017-10-04 18:05:01 +02001939 }
1940
Olivier Houchard0923fa42019-01-11 18:43:04 +01001941 if (check->server) {
1942 if (check->result == CHK_RES_FAILED) {
1943 /* a failure or timeout detected */
1944 check_notify_failure(check);
1945 }
1946 else if (check->result == CHK_RES_CONDPASS) {
1947 /* check is OK but asks for stopping mode */
1948 check_notify_stopping(check);
1949 }
1950 else if (check->result == CHK_RES_PASSED) {
1951 /* a success was detected */
1952 check_notify_success(check);
1953 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02001954 }
Christopher Faulet88ce5d12017-10-20 15:41:18 +02001955 task_set_affinity(t, MAX_THREADS_MASK);
Willy Tarreau2c115e52013-12-11 19:41:16 +01001956 check->state &= ~CHK_ST_INPROGRESS;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02001957
Olivier Houchard0923fa42019-01-11 18:43:04 +01001958 if (check->server) {
1959 rv = 0;
1960 if (global.spread_checks > 0) {
1961 rv = srv_getinter(check) * global.spread_checks / 100;
Willy Tarreau5a6d3e72020-03-08 17:53:53 +01001962 rv -= (int) (2 * rv * (ha_random32() / 4294967295.0));
Olivier Houchard0923fa42019-01-11 18:43:04 +01001963 }
1964 t->expire = tick_add(now_ms, MS_TO_TICKS(srv_getinter(check) + rv));
Willy Tarreaubaaee002006-06-26 02:48:02 +02001965 }
Willy Tarreaubaaee002006-06-26 02:48:02 +02001966 }
Willy Tarreau5a78f362012-11-23 12:47:05 +01001967
1968 reschedule:
1969 while (tick_is_expired(t->expire, now_ms))
Simon Horman4a741432013-02-23 15:35:38 +09001970 t->expire = tick_add(t->expire, MS_TO_TICKS(check->inter));
Willy Tarreau62ac84f2017-11-05 10:11:13 +01001971 out_unlock:
Olivier Houchard0923fa42019-01-11 18:43:04 +01001972 if (check->server)
1973 HA_SPIN_UNLOCK(SERVER_LOCK, &check->server->lock);
Willy Tarreau26c25062009-03-08 09:38:41 +01001974 return t;
Willy Tarreaubaaee002006-06-26 02:48:02 +02001975}
1976
Simon Horman98637e52014-06-20 12:30:16 +09001977/*
1978 * manages a server health-check. Returns
1979 * the time the task accepts to wait, or TIME_ETERNITY for infinity.
1980 */
Olivier Houchard9f6af332018-05-25 14:04:04 +02001981static struct task *process_chk(struct task *t, void *context, unsigned short state)
Simon Horman98637e52014-06-20 12:30:16 +09001982{
Olivier Houchard9f6af332018-05-25 14:04:04 +02001983 struct check *check = context;
Simon Horman98637e52014-06-20 12:30:16 +09001984
1985 if (check->type == PR_O2_EXT_CHK)
Olivier Houchard9f6af332018-05-25 14:04:04 +02001986 return process_chk_proc(t, context, state);
1987 return process_chk_conn(t, context, state);
Baptiste Assmanna68ca962015-04-14 01:15:08 +02001988
Simon Horman98637e52014-06-20 12:30:16 +09001989}
1990
Simon Horman5c942422013-11-25 10:46:32 +09001991static int start_check_task(struct check *check, int mininter,
1992 int nbcheck, int srvpos)
1993{
1994 struct task *t;
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02001995 unsigned long thread_mask = MAX_THREADS_MASK;
1996
1997 if (check->type == PR_O2_EXT_CHK)
1998 thread_mask = 1;
1999
Simon Horman5c942422013-11-25 10:46:32 +09002000 /* task for the check */
Willy Tarreau6dd4ac82019-09-03 18:55:02 +02002001 if ((t = task_new(thread_mask)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002002 ha_alert("Starting [%s:%s] check: out of memory.\n",
2003 check->server->proxy->id, check->server->id);
Simon Horman5c942422013-11-25 10:46:32 +09002004 return 0;
2005 }
2006
2007 check->task = t;
2008 t->process = process_chk;
2009 t->context = check;
2010
Willy Tarreau1746eec2014-04-25 10:46:47 +02002011 if (mininter < srv_getinter(check))
2012 mininter = srv_getinter(check);
2013
2014 if (global.max_spread_checks && mininter > global.max_spread_checks)
2015 mininter = global.max_spread_checks;
2016
Simon Horman5c942422013-11-25 10:46:32 +09002017 /* check this every ms */
Willy Tarreau1746eec2014-04-25 10:46:47 +02002018 t->expire = tick_add(now_ms, MS_TO_TICKS(mininter * srvpos / nbcheck));
Simon Horman5c942422013-11-25 10:46:32 +09002019 check->start = now;
2020 task_queue(t);
2021
2022 return 1;
2023}
2024
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002025/*
2026 * Start health-check.
Willy Tarreau865c5142016-12-21 20:04:48 +01002027 * Returns 0 if OK, ERR_FATAL on error, and prints the error in this case.
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002028 */
Willy Tarreau865c5142016-12-21 20:04:48 +01002029static int start_checks()
2030{
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002031
2032 struct proxy *px;
2033 struct server *s;
2034 struct task *t;
Simon Horman4a741432013-02-23 15:35:38 +09002035 int nbcheck=0, mininter=0, srvpos=0;
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002036
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002037 /* 0- init the dummy frontend used to create all checks sessions */
2038 init_new_proxy(&checks_fe);
2039 checks_fe.cap = PR_CAP_FE | PR_CAP_BE;
2040 checks_fe.mode = PR_MODE_TCP;
2041 checks_fe.maxconn = 0;
2042 checks_fe.conn_retries = CONN_RETRIES;
2043 checks_fe.options2 |= PR_O2_INDEPSTR | PR_O2_SMARTCON | PR_O2_SMARTACC;
2044 checks_fe.timeout.client = TICK_ETERNITY;
2045
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002046 /* 1- count the checkers to run simultaneously.
2047 * We also determine the minimum interval among all of those which
2048 * have an interval larger than SRV_CHK_INTER_THRES. This interval
2049 * will be used to spread their start-up date. Those which have
Jamie Gloudon801a0a32012-08-25 00:18:33 -04002050 * a shorter interval will start independently and will not dictate
Willy Tarreau2c43a1e2007-10-14 23:05:39 +02002051 * too short an interval for all others.
2052 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002053 for (px = proxies_list; px; px = px->next) {
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002054 for (s = px->srv; s; s = s->next) {
Willy Tarreaue7b73482013-11-21 11:50:50 +01002055 if (s->slowstart) {
Emeric Brunc60def82017-09-27 14:59:38 +02002056 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002057 ha_alert("Starting [%s:%s] check: out of memory.\n", px->id, s->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002058 return ERR_ALERT | ERR_FATAL;
Willy Tarreaue7b73482013-11-21 11:50:50 +01002059 }
2060 /* We need a warmup task that will be called when the server
2061 * state switches from down to up.
2062 */
2063 s->warmup = t;
2064 t->process = server_warmup;
2065 t->context = s;
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002066 /* server can be in this state only because of */
Emeric Brun52a91d32017-08-31 14:41:55 +02002067 if (s->next_state == SRV_ST_STARTING)
Baptiste Assmann6076d1c2015-09-17 22:53:59 +02002068 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 +01002069 }
2070
Willy Tarreaud8514a22013-12-11 21:10:14 +01002071 if (s->check.state & CHK_ST_CONFIGURED) {
2072 nbcheck++;
2073 if ((srv_getinter(&s->check) >= SRV_CHK_INTER_THRES) &&
2074 (!mininter || mininter > srv_getinter(&s->check)))
2075 mininter = srv_getinter(&s->check);
2076 }
Willy Tarreau15f39102013-12-11 20:41:18 +01002077
Willy Tarreaud8514a22013-12-11 21:10:14 +01002078 if (s->agent.state & CHK_ST_CONFIGURED) {
2079 nbcheck++;
2080 if ((srv_getinter(&s->agent) >= SRV_CHK_INTER_THRES) &&
2081 (!mininter || mininter > srv_getinter(&s->agent)))
2082 mininter = srv_getinter(&s->agent);
2083 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002084 }
2085 }
2086
Simon Horman4a741432013-02-23 15:35:38 +09002087 if (!nbcheck)
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002088 return 0;
2089
2090 srand((unsigned)time(NULL));
2091
2092 /*
2093 * 2- start them as far as possible from each others. For this, we will
2094 * start them after their interval set to the min interval divided by
2095 * the number of servers, weighted by the server's position in the list.
2096 */
Olivier Houchardfbc74e82017-11-24 16:54:05 +01002097 for (px = proxies_list; px; px = px->next) {
Simon Horman98637e52014-06-20 12:30:16 +09002098 if ((px->options2 & PR_O2_CHK_ANY) == PR_O2_EXT_CHK) {
2099 if (init_pid_list()) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01002100 ha_alert("Starting [%s] check: out of memory.\n", px->id);
Willy Tarreau865c5142016-12-21 20:04:48 +01002101 return ERR_ALERT | ERR_FATAL;
Simon Horman98637e52014-06-20 12:30:16 +09002102 }
2103 }
2104
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002105 for (s = px->srv; s; s = s->next) {
Simon Hormand60d6912013-11-25 10:46:36 +09002106 /* A task for the main check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002107 if (s->check.state & CHK_ST_CONFIGURED) {
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002108 if (s->check.type == PR_O2_EXT_CHK) {
2109 if (!prepare_external_check(&s->check))
Willy Tarreau865c5142016-12-21 20:04:48 +01002110 return ERR_ALERT | ERR_FATAL;
Cyril Bonté99c5bf52014-08-07 01:55:38 +02002111 }
Simon Hormand60d6912013-11-25 10:46:36 +09002112 if (!start_check_task(&s->check, mininter, nbcheck, srvpos))
Willy Tarreau865c5142016-12-21 20:04:48 +01002113 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002114 srvpos++;
2115 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002116
Simon Hormand60d6912013-11-25 10:46:36 +09002117 /* A task for a auxiliary agent check */
Willy Tarreauff5ae352013-12-11 20:36:34 +01002118 if (s->agent.state & CHK_ST_CONFIGURED) {
Simon Hormand60d6912013-11-25 10:46:36 +09002119 if (!start_check_task(&s->agent, mininter, nbcheck, srvpos)) {
Willy Tarreau865c5142016-12-21 20:04:48 +01002120 return ERR_ALERT | ERR_FATAL;
Simon Hormand60d6912013-11-25 10:46:36 +09002121 }
2122 srvpos++;
2123 }
Krzysztof Oledzkib304dc72007-10-14 23:40:01 +02002124 }
2125 }
2126 return 0;
2127}
Willy Tarreaubaaee002006-06-26 02:48:02 +02002128
2129/*
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002130 * return the id of a step in a send/expect session
2131 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002132static int tcpcheck_get_step_id(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002133{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002134 if (!rule)
2135 rule = check->current_step;
Willy Tarreau213c6782014-10-02 14:51:02 +02002136
Christopher Faulet3c29aa62020-03-24 13:31:19 +01002137 /* no last started step => first step */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002138 if (!rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002139 return 1;
2140
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002141 /* last step is the first implicit connect */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002142 if (rule->index == 0 &&
2143 rule->action == TCPCHK_ACT_CONNECT &&
Christopher Fauletbb591a12020-04-01 16:52:17 +02002144 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002145 return 0;
2146
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002147 return rule->index + 1;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002148}
2149
Christopher Faulet206368d2020-04-03 14:51:06 +02002150static void tcpcheck_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2151 int match, struct ist info)
2152{
2153 struct sample *smp;
2154
2155 if (istlen(info)) {
2156 chunk_strncat(msg, info.ptr, info.len);
2157 goto comment;
2158 }
2159 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
2160 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
2161 goto comment;
2162 }
2163
Christopher Faulet799f3a42020-04-07 12:06:14 +02002164 if (check->type == PR_O2_TCPCHK_CHK && (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK))
2165 goto comment;
2166
Christopher Faulet206368d2020-04-03 14:51:06 +02002167 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
2168 switch (rule->expect.type) {
2169 case TCPCHK_EXPECT_STRING:
Christopher Faulete5870d82020-04-15 11:32:03 +02002170 case TCPCHK_EXPECT_HTTP_STATUS:
2171 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02002172 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), rule->expect.data.ptr,
2173 tcpcheck_get_step_id(check, rule));
Christopher Faulet206368d2020-04-03 14:51:06 +02002174 break;
2175 case TCPCHK_EXPECT_BINARY:
2176 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
2177 break;
2178 case TCPCHK_EXPECT_REGEX:
Christopher Faulete5870d82020-04-15 11:32:03 +02002179 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
2180 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Faulet206368d2020-04-03 14:51:06 +02002181 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
2182 break;
2183 case TCPCHK_EXPECT_REGEX_BINARY:
2184 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
2185
2186 /* If references to the matched text were made, divide the
2187 * offsets by 2 to match offset of the original response buffer.
2188 */
Christopher Faulet12d57402020-04-10 09:58:42 +02002189 if (rule->expect.flags & TCPCHK_EXPT_FL_CAP) {
Christopher Faulet206368d2020-04-03 14:51:06 +02002190 int i;
2191
2192 for (i = 1; i < MAX_MATCH && pmatch[i].rm_so != -1; i++) {
2193 pmatch[i].rm_so /= 2; /* at first matched char. */
2194 pmatch[i].rm_eo /= 2; /* at last matched char. */
2195 }
2196 }
2197 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02002198 case TCPCHK_EXPECT_CUSTOM:
2199 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
2200 break;
Christopher Faulet206368d2020-04-03 14:51:06 +02002201 case TCPCHK_EXPECT_UNDEF:
2202 /* Should never happen. */
2203 return;
2204 }
2205
2206 comment:
2207 if (rule->comment) {
2208 chunk_strcat(msg, " comment: ");
Christopher Faulet12d57402020-04-10 09:58:42 +02002209 if (rule->expect.flags & TCPCHK_EXPT_FL_CAP) {
Christopher Faulet206368d2020-04-03 14:51:06 +02002210 int ret = exp_replace(b_tail(msg), b_room(msg), b_head(&check->bi), rule->comment, pmatch);
2211 if (ret != -1) /* ignore comment if too large */
2212 msg->data += ret;
2213 }
2214 else
2215 chunk_strcat(msg, rule->comment);
2216 }
2217
2218 if (rule->expect.status_expr) {
2219 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2220 rule->expect.status_expr, SMP_T_SINT);
2221 if (smp)
2222 check->code = smp->data.u.sint;
2223 }
2224
2225 *(b_tail(msg)) = '\0';
2226}
2227
2228static void tcpcheck_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
2229 struct ist info)
2230{
2231 struct sample *smp;
2232
2233 if (istlen(info))
2234 chunk_strncat(msg, info.ptr, info.len);
2235 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
2236 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
2237 &rule->expect.onsuccess_fmt);
Christopher Faulet799f3a42020-04-07 12:06:14 +02002238 else if (check->type == PR_O2_TCPCHK_CHK && !(check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK))
Christopher Faulet206368d2020-04-03 14:51:06 +02002239 chunk_strcat(msg, "(tcp-check)");
2240
2241 if (rule->expect.status_expr) {
2242 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
2243 rule->expect.status_expr, SMP_T_SINT);
2244 if (smp)
2245 check->code = smp->data.u.sint;
2246 }
2247
2248 *(b_tail(msg)) = '\0';
2249}
2250
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002251static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
2252 unsigned int offset, int last_read)
2253{
2254 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2255 enum healthcheck_status status;
2256 struct buffer *msg = NULL;
2257 struct ist desc = ist(NULL);
2258 unsigned int err = 0, plen = 0;
2259
2260
2261 /* 3 Bytes for the packet length and 1 byte for the sequence id */
2262 if (!last_read && b_data(&check->bi) < offset+4) {
2263 if (!last_read)
2264 goto wait_more_data;
2265
2266 /* invalid length or truncated response */
2267 status = HCHK_STATUS_L7RSP;
2268 goto error;
2269 }
2270
2271 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
2272 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
2273 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
2274
2275 if (b_data(&check->bi) < offset+plen+4) {
2276 if (!last_read)
2277 goto wait_more_data;
2278
2279 /* invalid length or truncated response */
2280 status = HCHK_STATUS_L7RSP;
2281 goto error;
2282 }
2283
2284 if (*b_peek(&check->bi, offset+4) == '\xff') {
2285 /* MySQL Error packet always begin with field_count = 0xff */
2286 status = HCHK_STATUS_L7STS;
2287 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
2288 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
2289 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
2290 goto error;
2291 }
2292
2293 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
2294 /* Not the last rule, continue */
2295 goto out;
2296 }
2297
2298 /* We set the MySQL Version in description for information purpose
2299 * FIXME : it can be cool to use MySQL Version for other purpose,
2300 * like mark as down old MySQL server.
2301 */
Christopher Fauletec07e382020-04-07 14:56:26 +02002302 set_server_check_status(check, rule->expect.ok_status, b_peek(&check->bi, 5));
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002303
2304 out:
2305 free_trash_chunk(msg);
2306 return ret;
2307
2308 error:
2309 ret = TCPCHK_EVAL_STOP;
2310 check->code = err;
2311 msg = alloc_trash_chunk();
2312 if (msg)
2313 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2314 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2315 goto out;
2316
2317 wait_more_data:
2318 ret = TCPCHK_EVAL_WAIT;
2319 goto out;
2320}
2321
2322
2323static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
2324{
2325 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
2326}
2327
2328static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
2329{
2330 unsigned int hslen = 0;
2331
2332 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
2333 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
2334 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
2335
2336 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
2337}
2338
Christopher Faulet1997eca2020-04-03 23:13:50 +02002339static enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
2340{
2341 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2342 enum healthcheck_status status;
2343 struct buffer *msg = NULL;
2344 struct ist desc = ist(NULL);
2345 unsigned short msglen = 0;
2346
2347 /* Check if the server speaks LDAP (ASN.1/BER)
2348 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
2349 * http://tools.ietf.org/html/rfc4511
2350 */
2351 /* size of LDAPMessage */
2352 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
2353
2354 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
2355 * messageID: 0x02 0x01 0x01: INTEGER 1
2356 * protocolOp: 0x61: bindResponse
2357 */
2358 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
2359 status = HCHK_STATUS_L7RSP;
2360 desc = ist("Not LDAPv3 protocol");
2361 goto error;
2362 }
2363
2364 /* size of bindResponse */
2365 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
2366
2367 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2368 * ldapResult: 0x0a 0x01: ENUMERATION
2369 */
2370 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
2371 status = HCHK_STATUS_L7RSP;
2372 desc = ist("Not LDAPv3 protocol");
2373 goto error;
2374 }
2375
2376 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
2377 * resultCode
2378 */
2379 check->code = *(b_head(&check->bi) + msglen + 9);
2380 if (check->code) {
2381 status = HCHK_STATUS_L7STS;
2382 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
2383 goto error;
2384 }
2385
Christopher Fauletec07e382020-04-07 14:56:26 +02002386 set_server_check_status(check, rule->expect.ok_status, "Success");
Christopher Faulet1997eca2020-04-03 23:13:50 +02002387
2388 out:
2389 free_trash_chunk(msg);
2390 return ret;
2391
2392 error:
2393 ret = TCPCHK_EVAL_STOP;
2394 msg = alloc_trash_chunk();
2395 if (msg)
2396 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2397 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2398 goto out;
2399
2400 wait_more_data:
2401 ret = TCPCHK_EVAL_WAIT;
2402 goto out;
2403}
Christopher Fauletf2b3be52020-04-02 18:07:37 +02002404
Christopher Faulet267b01b2020-04-04 10:27:09 +02002405
2406static enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, 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 framesz;
2413
2414
2415 memcpy(&framesz, b_head(&check->bi), 4);
2416 framesz = ntohl(framesz);
2417
2418 if (!last_read && b_data(&check->bi) < (4+framesz))
2419 goto wait_more_data;
2420
2421 memset(b_orig(&trash), 0, b_size(&trash));
2422 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
2423 status = HCHK_STATUS_L7RSP;
2424 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
2425 goto error;
2426 }
2427
Christopher Fauletec07e382020-04-07 14:56:26 +02002428 set_server_check_status(check, rule->expect.ok_status, "SPOA server is ok");
Christopher Faulet267b01b2020-04-04 10:27:09 +02002429
2430 out:
2431 free_trash_chunk(msg);
2432 return ret;
2433
2434 error:
2435 ret = TCPCHK_EVAL_STOP;
2436 msg = alloc_trash_chunk();
2437 if (msg)
2438 tcpcheck_onerror_message(msg, check, rule, 0, desc);
2439 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2440 goto out;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02002441
2442 wait_more_data:
2443 ret = TCPCHK_EVAL_WAIT;
2444 goto out;
2445}
2446
2447static enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
2448{
2449 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
2450 enum healthcheck_status status = HCHK_STATUS_CHECKED;
2451 const char *hs = NULL; /* health status */
2452 const char *as = NULL; /* admin status */
2453 const char *ps = NULL; /* performance status */
2454 const char *cs = NULL; /* maxconn */
2455 const char *err = NULL; /* first error to report */
2456 const char *wrn = NULL; /* first warning to report */
2457 char *cmd, *p;
2458
2459 /* We're getting an agent check response. The agent could
2460 * have been disabled in the mean time with a long check
2461 * still pending. It is important that we ignore the whole
2462 * response.
2463 */
2464 if (!(check->state & CHK_ST_ENABLED))
2465 goto out;
2466
2467 /* The agent supports strings made of a single line ended by the
2468 * first CR ('\r') or LF ('\n'). This line is composed of words
2469 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
2470 * line may optionally contained a description of a state change
2471 * after a sharp ('#'), which is only considered if a health state
2472 * is announced.
2473 *
2474 * Words may be composed of :
2475 * - a numeric weight suffixed by the percent character ('%').
2476 * - a health status among "up", "down", "stopped", and "fail".
2477 * - an admin status among "ready", "drain", "maint".
2478 *
2479 * These words may appear in any order. If multiple words of the
2480 * same category appear, the last one wins.
2481 */
2482
2483 p = b_head(&check->bi);
2484 while (*p && *p != '\n' && *p != '\r')
2485 p++;
2486
2487 if (!*p) {
2488 if (!last_read)
2489 goto wait_more_data;
2490
2491 /* at least inform the admin that the agent is mis-behaving */
2492 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
2493 goto out;
2494 }
2495
2496 *p = 0;
2497 cmd = b_head(&check->bi);
2498
2499 while (*cmd) {
2500 /* look for next word */
2501 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
2502 cmd++;
2503 continue;
2504 }
2505
2506 if (*cmd == '#') {
2507 /* this is the beginning of a health status description,
2508 * skip the sharp and blanks.
2509 */
2510 cmd++;
2511 while (*cmd == '\t' || *cmd == ' ')
2512 cmd++;
2513 break;
2514 }
2515
2516 /* find the end of the word so that we have a null-terminated
2517 * word between <cmd> and <p>.
2518 */
2519 p = cmd + 1;
2520 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
2521 p++;
2522 if (*p)
2523 *p++ = 0;
2524
2525 /* first, health statuses */
2526 if (strcasecmp(cmd, "up") == 0) {
2527 check->server->check.health = check->server->check.rise + check->server->check.fall - 1;
2528 status = HCHK_STATUS_L7OKD;
2529 hs = cmd;
2530 }
2531 else if (strcasecmp(cmd, "down") == 0) {
2532 check->server->check.health = 0;
2533 status = HCHK_STATUS_L7STS;
2534 hs = cmd;
2535 }
2536 else if (strcasecmp(cmd, "stopped") == 0) {
2537 check->server->check.health = 0;
2538 status = HCHK_STATUS_L7STS;
2539 hs = cmd;
2540 }
2541 else if (strcasecmp(cmd, "fail") == 0) {
2542 check->server->check.health = 0;
2543 status = HCHK_STATUS_L7STS;
2544 hs = cmd;
2545 }
2546 /* admin statuses */
2547 else if (strcasecmp(cmd, "ready") == 0) {
2548 as = cmd;
2549 }
2550 else if (strcasecmp(cmd, "drain") == 0) {
2551 as = cmd;
2552 }
2553 else if (strcasecmp(cmd, "maint") == 0) {
2554 as = cmd;
2555 }
2556 /* try to parse a weight here and keep the last one */
2557 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
2558 ps = cmd;
2559 }
2560 /* try to parse a maxconn here */
2561 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
2562 cs = cmd;
2563 }
2564 else {
2565 /* keep a copy of the first error */
2566 if (!err)
2567 err = cmd;
2568 }
2569 /* skip to next word */
2570 cmd = p;
2571 }
2572 /* here, cmd points either to \0 or to the beginning of a
2573 * description. Skip possible leading spaces.
2574 */
2575 while (*cmd == ' ' || *cmd == '\n')
2576 cmd++;
2577
2578 /* First, update the admin status so that we avoid sending other
2579 * possibly useless warnings and can also update the health if
2580 * present after going back up.
2581 */
2582 if (as) {
2583 if (strcasecmp(as, "drain") == 0)
2584 srv_adm_set_drain(check->server);
2585 else if (strcasecmp(as, "maint") == 0)
2586 srv_adm_set_maint(check->server);
2587 else
2588 srv_adm_set_ready(check->server);
2589 }
2590
2591 /* now change weights */
2592 if (ps) {
2593 const char *msg;
2594
2595 msg = server_parse_weight_change_request(check->server, ps);
2596 if (!wrn || !*wrn)
2597 wrn = msg;
2598 }
2599
2600 if (cs) {
2601 const char *msg;
2602
2603 cs += strlen("maxconn:");
2604
2605 msg = server_parse_maxconn_change_request(check->server, cs);
2606 if (!wrn || !*wrn)
2607 wrn = msg;
2608 }
2609
2610 /* and finally health status */
2611 if (hs) {
2612 /* We'll report some of the warnings and errors we have
2613 * here. Down reports are critical, we leave them untouched.
2614 * Lack of report, or report of 'UP' leaves the room for
2615 * ERR first, then WARN.
2616 */
2617 const char *msg = cmd;
2618 struct buffer *t;
2619
2620 if (!*msg || status == HCHK_STATUS_L7OKD) {
2621 if (err && *err)
2622 msg = err;
2623 else if (wrn && *wrn)
2624 msg = wrn;
2625 }
2626
2627 t = get_trash_chunk();
2628 chunk_printf(t, "via agent : %s%s%s%s",
2629 hs, *msg ? " (" : "",
2630 msg, *msg ? ")" : "");
2631 set_server_check_status(check, status, t->area);
2632 }
2633 else if (err && *err) {
2634 /* No status change but we'd like to report something odd.
2635 * Just report the current state and copy the message.
2636 */
2637 chunk_printf(&trash, "agent reports an error : %s", err);
2638 set_server_check_status(check, status/*check->status*/, trash.area);
2639 }
2640 else if (wrn && *wrn) {
2641 /* No status change but we'd like to report something odd.
2642 * Just report the current state and copy the message.
2643 */
2644 chunk_printf(&trash, "agent warns : %s", wrn);
2645 set_server_check_status(check, status/*check->status*/, trash.area);
2646 }
2647 else
2648 set_server_check_status(check, status, NULL);
2649
2650 out:
2651 return ret;
Christopher Faulet267b01b2020-04-04 10:27:09 +02002652
2653 wait_more_data:
2654 ret = TCPCHK_EVAL_WAIT;
2655 goto out;
2656}
2657
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002658/* Evaluate a TCPCHK_ACT_CONNECT rule. It returns 1 to evaluate the next rule, 0
2659 * to wait and -1 to stop the check. */
2660static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002661{
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002662 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2663 struct tcpcheck_connect *connect = &rule->connect;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01002664 struct proxy *proxy = check->proxy;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002665 struct server *s = check->server;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002666 struct task *t = check->task;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002667 struct conn_stream *cs;
2668 struct connection *conn = NULL;
2669 struct protocol *proto;
2670 struct xprt_ops *xprt;
Christopher Faulet5c288742020-03-31 08:15:58 +02002671 int status, port;
Willy Tarreauef953952014-10-02 14:30:14 +02002672
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002673 /* For a connect action we'll create a new connection. We may also have
2674 * to kill a previous one. But we don't want to leave *without* a
2675 * connection if we came here from the connection layer, hence with a
2676 * connection. Thus we'll proceed in the following order :
2677 * 1: close but not release previous connection (handled by the caller)
2678 * 2: try to get a new connection
2679 * 3: release and replace the old one on success
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002680 */
Willy Tarreau449f9522015-05-13 15:39:48 +02002681
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002682 /* 2- prepare new connection */
2683 cs = cs_new(NULL);
2684 if (!cs) {
2685 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
2686 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002687 if (rule->comment)
2688 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002689 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2690 ret = TCPCHK_EVAL_STOP;
Christopher Fauletb6102852017-11-28 10:06:29 +01002691 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002692 }
2693
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002694 /* 3- release and replace the old one on success */
2695 if (check->cs) {
2696 if (check->wait_list.events)
2697 cs->conn->xprt->unsubscribe(cs->conn, cs->conn->xprt_ctx,
2698 check->wait_list.events, &check->wait_list);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02002699
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002700 /* We may have been scheduled to run, and the I/O handler
2701 * expects to have a cs, so remove the tasklet
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01002702 */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002703 tasklet_remove_from_tasklet_list(check->wait_list.tasklet);
2704 cs_destroy(check->cs);
2705 }
Willy Tarreaudeccd112018-06-14 18:38:55 +02002706
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002707 tasklet_set_tid(check->wait_list.tasklet, tid);
Willy Tarreauabca5b62013-12-06 14:19:25 +01002708
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002709 check->cs = cs;
2710 conn = cs->conn;
Willy Tarreauabca5b62013-12-06 14:19:25 +01002711
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002712 /* Maybe there were an older connection we were waiting on */
2713 check->wait_list.events = 0;
2714 conn->target = s ? &s->obj_type : &proxy->obj_type;
Willy Tarreauf3d34822014-12-08 12:11:28 +01002715
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002716 /* no client address */
2717 if (!sockaddr_alloc(&conn->dst)) {
2718 status = SF_ERR_RESOURCE;
2719 goto fail_check;
2720 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002721
Christopher Faulet5c288742020-03-31 08:15:58 +02002722 /* connect to the connect rule addr if specified, otherwise the check
2723 * addr if specified on the server. otherwise, use the server addr
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002724 */
Christopher Faulet5c288742020-03-31 08:15:58 +02002725 *conn->dst = (is_addr(&connect->addr)
2726 ? connect->addr
2727 : (is_addr(&check->addr) ? check->addr : s->addr));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002728 proto = protocol_by_family(conn->dst->ss_family);
Willy Tarreau00149122017-10-04 18:05:01 +02002729
Christopher Faulet5c288742020-03-31 08:15:58 +02002730 port = 0;
2731 if (!port && connect->port)
2732 port = connect->port;
Christopher Fauletb7d30092020-03-30 15:19:03 +02002733 if (!port && connect->port_expr) {
2734 struct sample *smp;
2735
2736 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
2737 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
2738 connect->port_expr, SMP_T_SINT);
2739 if (smp)
2740 port = smp->data.u.sint;
2741 }
Christopher Faulet5c288742020-03-31 08:15:58 +02002742 if (!port && is_inet_addr(&connect->addr))
2743 port = get_host_port(&connect->addr);
2744 if (!port && check->port)
2745 port = check->port;
2746 if (!port && is_inet_addr(&check->addr))
2747 port = get_host_port(&check->addr);
2748 if (!port)
2749 port = s->svc_port;
2750 set_host_port(conn->dst, port);
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002751
Christopher Fauletbb591a12020-04-01 16:52:17 +02002752 xprt = ((connect->options & TCPCHK_OPT_SSL)
2753 ? xprt_get(XPRT_SSL)
2754 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
Willy Tarreau00149122017-10-04 18:05:01 +02002755
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002756 conn_prepare(conn, proto, xprt);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01002757 if (conn_install_mux(conn, &mux_pt_ops, cs, proxy, check->sess) < 0) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002758 status = SF_ERR_RESOURCE;
2759 goto fail_check;
2760 }
2761 cs_attach(cs, check, &check_conn_cb);
Willy Tarreau00149122017-10-04 18:05:01 +02002762
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002763 status = SF_ERR_INTERNAL;
2764 if (proto && proto->connect) {
2765 struct tcpcheck_rule *next;
2766 int flags = CONNECT_HAS_DATA;
Olivier Houchardff1e9f32019-09-20 17:18:35 +02002767
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002768 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
2769 if (!next || next->action != TCPCHK_ACT_EXPECT)
2770 flags |= CONNECT_DELACK_ALWAYS;
2771 status = proto->connect(conn, flags);
2772 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002773
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002774#ifdef USE_OPENSSL
Christopher Fauletbb591a12020-04-01 16:52:17 +02002775 if (status == SF_ERR_NONE) {
2776 if (connect->sni)
2777 ssl_sock_set_servername(conn, connect->sni);
2778 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
2779 ssl_sock_set_servername(conn, s->check.sni);
2780
2781 if (connect->alpn)
2782 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
2783 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
2784 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002785 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02002786#endif
Christopher Fauletbb591a12020-04-01 16:52:17 +02002787 if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
2788 conn->send_proxy_ofs = 1;
2789 conn->flags |= CO_FL_SOCKS4;
2790 }
2791 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
2792 conn->send_proxy_ofs = 1;
2793 conn->flags |= CO_FL_SOCKS4;
2794 }
2795
2796 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
2797 conn->send_proxy_ofs = 1;
2798 conn->flags |= CO_FL_SEND_PROXY;
2799 }
2800 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
2801 conn->send_proxy_ofs = 1;
2802 conn->flags |= CO_FL_SEND_PROXY;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002803 }
Willy Tarreauca79f592019-07-17 19:04:47 +02002804
Christopher Fauletbb591a12020-04-01 16:52:17 +02002805 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
2806 /* Some servers don't like reset on close */
2807 fdtab[cs->conn->handle.fd].linger_risk = 0;
2808 }
2809
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002810 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
2811 if (xprt_add_hs(conn) < 0)
2812 status = SF_ERR_RESOURCE;
2813 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002814
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002815 fail_check:
2816 /* It can return one of :
2817 * - SF_ERR_NONE if everything's OK
2818 * - SF_ERR_SRVTO if there are no more servers
2819 * - SF_ERR_SRVCL if the connection was refused by the server
2820 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
2821 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
2822 * - SF_ERR_INTERNAL for any other purely internal errors
2823 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
2824 * Note that we try to prevent the network stack from sending the ACK during the
2825 * connect() when a pure TCP check is used (without PROXY protocol).
2826 */
2827 switch (status) {
2828 case SF_ERR_NONE:
2829 /* we allow up to min(inter, timeout.connect) for a connection
2830 * to establish but only when timeout.check is set as it may be
2831 * to short for a full check otherwise
2832 */
2833 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002834
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002835 if (proxy->timeout.check && proxy->timeout.connect) {
2836 int t_con = tick_add(now_ms, proxy->timeout.connect);
2837 t->expire = tick_first(t->expire, t_con);
2838 }
2839 break;
2840 case SF_ERR_SRVTO: /* ETIMEDOUT */
2841 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
2842 chunk_printf(&trash, "TCPCHK error establishing connection at step %d: %s",
2843 tcpcheck_get_step_id(check, rule), strerror(errno));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002844 if (rule->comment)
2845 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002846 set_server_check_status(check, HCHK_STATUS_L4CON, trash.area);
2847 ret = TCPCHK_EVAL_STOP;
2848 goto out;
2849 case SF_ERR_PRXCOND:
2850 case SF_ERR_RESOURCE:
2851 case SF_ERR_INTERNAL:
2852 chunk_printf(&trash, "TCPCHK error establishing connection at step %d",
2853 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02002854 if (rule->comment)
2855 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002856 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2857 ret = TCPCHK_EVAL_STOP;
2858 goto out;
2859 }
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002860
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002861 /* don't do anything until the connection is established */
2862 if (conn->flags & CO_FL_WAIT_XPRT) {
2863 ret = TCPCHK_EVAL_WAIT;
2864 goto out;
2865 }
Willy Tarreaube373152018-09-06 11:45:30 +02002866
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002867 out:
2868 if (conn && check->result == CHK_RES_FAILED)
2869 conn->flags |= CO_FL_ERROR;
2870 return ret;
2871}
Willy Tarreau2ab5c382019-07-17 18:48:07 +02002872
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002873/* Evaluate a TCPCHK_ACT_SEND rule. It returns 1 to evaluate the next rule, 0
2874 * to wait and -1 to stop the check. */
2875static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
2876{
2877 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2878 struct tcpcheck_send *send = &rule->send;
2879 struct conn_stream *cs = check->cs;
2880 struct connection *conn = cs_conn(cs);
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002881 struct buffer *tmp = NULL;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002882
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002883 /* reset the read & write buffer */
2884 b_reset(&check->bi);
2885 b_reset(&check->bo);
Gaetan Rivet08fdcb32020-02-28 11:04:21 +01002886
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002887 switch (send->type) {
2888 case TCPCHK_SEND_STRING:
2889 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002890 if (istlen(send->data) >= b_size(&check->bo)) {
2891 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
2892 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
2893 tcpcheck_get_step_id(check, rule));
2894 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2895 ret = TCPCHK_EVAL_STOP;
2896 goto out;
2897 }
2898 b_putist(&check->bo, send->data);
2899 break;
2900 case TCPCHK_SEND_STRING_LF:
2901 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
2902 if (!b_data(&check->bo))
2903 goto out;
2904 break;
2905 case TCPCHK_SEND_BINARY_LF:
2906 tmp = alloc_trash_chunk();
2907 if (!tmp)
2908 goto error_lf;
2909 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
2910 if (!b_data(tmp))
2911 goto out;
2912 tmp->area[tmp->data] = '\0';
2913 b_set_data(&check->bo, b_size(&check->bo));
2914 if (parse_binary(b_orig(tmp), &check->bo.area, (int *)&check->bo.data, NULL) == 0)
2915 goto error_lf;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002916 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02002917 case TCPCHK_SEND_HTTP: {
2918 struct ist meth, uri, vsn;
2919
2920 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
2921 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
2922 : http_known_methods[send->http.meth.meth]);
2923 uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
2924 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
2925
2926 chunk_istcat(&check->bo, meth);
2927 check->bo.area[check->bo.data++] = ' ';
2928 chunk_istcat(&check->bo, uri);
2929 check->bo.area[check->bo.data++] = ' ';
2930 chunk_istcat(&check->bo, vsn);
2931 chunk_istcat(&check->bo, ist("\r\n"));
2932 chunk_istcat(&check->bo, ist("Connection: close\r\n"));
2933 if (isttest(send->http.body)) {
2934 // TODO: handle body_fmt
2935 chunk_appendf(&check->bo, "Content-Length: %s\r\n", ultoa(istlen(send->http.body)));
2936 }
2937 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
2938 trash.data = httpchk_build_status_header(check->server, b_orig(&trash), b_size(&trash));
2939 chunk_cat(&check->bo, &trash);
2940 }
2941 if (!LIST_ISEMPTY(&send->http.hdrs)) {
2942 struct tcpcheck_http_hdr *hdr;
2943
2944 tmp = alloc_trash_chunk();
2945 if (!tmp)
2946 goto error_lf;
2947 list_for_each_entry(hdr, &send->http.hdrs, list) {
2948 chunk_reset(tmp);
2949 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
2950 if (!b_data(tmp))
2951 continue;
2952 chunk_istcat(&check->bo, hdr->name);
2953 check->bo.area[check->bo.data++] = ' ';
2954 chunk_cat(&check->bo, tmp);
2955 chunk_istcat(&check->bo, ist("\r\n"));
2956 }
2957 }
2958 chunk_istcat(&check->bo, ist("\r\n"));
2959 if (isttest(send->http.body)) {
2960 // TODO: handle body_fmt
2961 chunk_istcat(&check->bo, send->http.body);
2962 }
2963 break;
2964 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002965 case TCPCHK_SEND_UNDEF:
2966 /* Should never happen. */
2967 ret = TCPCHK_EVAL_STOP;
2968 goto out;
2969 };
Christopher Fauleta202d1d2020-03-26 17:38:49 +01002970
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002971 if (conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0) <= 0) {
2972 ret = TCPCHK_EVAL_WAIT;
2973 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))
2974 ret = TCPCHK_EVAL_STOP;
2975 goto out;
2976 }
2977 if (b_data(&check->bo)) {
2978 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
2979 ret = TCPCHK_EVAL_WAIT;
2980 goto out;
2981 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01002982
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002983 out:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002984 free_trash_chunk(tmp);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02002985 return ret;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02002986
2987 error_lf:
2988 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
2989 tcpcheck_get_step_id(check, rule));
2990 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2991 ret = TCPCHK_EVAL_STOP;
Christopher Faulete5870d82020-04-15 11:32:03 +02002992 goto out;
2993
2994}
2995
Christopher Fauletf9585d82020-04-16 13:25:58 +02002996/* */
2997static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
2998{
2999 struct conn_stream *cs = check->cs;
3000 struct connection *conn = cs_conn(cs);
3001 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3002 size_t max, read, cur_read = 0;
3003 int is_empty;
3004 int read_poll = MAX_READ_POLL_LOOPS;
3005
3006 if (check->wait_list.events & SUB_RETRY_RECV)
3007 goto wait_more_data;
3008
3009 if (cs->flags & CS_FL_EOS)
3010 goto end_recv;
3011
3012 /* errors on the connection and the conn-stream were already checked */
3013
3014 /* prepare to detect if the mux needs more room */
3015 cs->flags &= ~CS_FL_WANT_ROOM;
3016
3017 while ((cs->flags & CS_FL_RCV_MORE) ||
3018 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
3019 max = b_room(&check->bi);
3020 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
3021 cur_read += read;
3022 if (!read ||
3023 (cs->flags & CS_FL_WANT_ROOM) ||
3024 (--read_poll <= 0) ||
3025 (read < max && read >= global.tune.recv_enough))
3026 break;
3027 }
3028
3029 end_recv:
3030 is_empty = !b_data(&check->bi);
3031 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
3032 /* Report network errors only if we got no other data. Otherwise
3033 * we'll let the upper layers decide whether the response is OK
3034 * or not. It is very common that an RST sent by the server is
3035 * reported as an error just after the last data chunk.
3036 */
3037 goto stop;
3038 }
3039 if (!cur_read) {
3040 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
3041 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
3042 goto wait_more_data;
3043 }
3044 if (is_empty) {
3045 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
3046 tcpcheck_get_step_id(check, rule));
3047 if (rule->comment)
3048 chunk_appendf(&trash, " comment: '%s'", rule->comment);
3049 set_server_check_status(check, rule->expect.err_status, trash.area);
3050 goto stop;
3051 }
3052 }
3053
3054 out:
3055 return ret;
3056
3057 stop:
3058 ret = TCPCHK_EVAL_STOP;
3059 goto out;
3060
3061 wait_more_data:
3062 ret = TCPCHK_EVAL_WAIT;
3063 goto out;
3064}
3065
Christopher Faulete5870d82020-04-15 11:32:03 +02003066static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
3067{
3068 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3069 struct tcpcheck_expect *expect = &rule->expect;
3070 struct buffer *msg = NULL;
3071 enum healthcheck_status status;
3072 struct ist desc = ist(NULL);
3073 char *body;
3074 size_t body_len;
3075 int match, inverse;
3076
3077 last_read |= b_full(&check->bi);
3078
3079 /* Must at least receive the status line (HTTP/1.X XXX.) */
3080 if (!last_read && b_data(&check->bi) < 13)
3081 goto wait_more_data;
3082
3083 /* Check if the server speaks HTTP 1.X */
3084 if (b_data(&check->bi) < 13 ||
3085 memcmp(b_head(&check->bi), "HTTP/1.", 7) != 0 ||
3086 (*b_peek(&check->bi, 12) != ' ' && *b_peek(&check->bi, 12) != '\r') ||
3087 !isdigit((unsigned char) *b_peek(&check->bi, 9)) || !isdigit((unsigned char) *b_peek(&check->bi, 10)) ||
3088 !isdigit((unsigned char) *b_peek(&check->bi, 11))) {
3089 status = HCHK_STATUS_L7RSP;
3090 desc = ist2(b_head(&check->bi), my_memcspn(b_head(&check->bi), b_data(&check->bi), "\r\n", 2));
3091 goto error;
3092 }
3093
3094 check->code = strl2uic(b_peek(&check->bi, 9), 3);
3095
3096 if (check->server &&
3097 (check->server->proxy->options & PR_O_DISABLE404) &&
3098 (check->server->next_state != SRV_ST_STOPPED) &&
3099 (check->code == 404)) {
3100 /* 404 may be accepted as "stopping" only if the server was up */
3101 goto out;
3102 }
3103
3104 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
3105 /* Make GCC happy ; initialize match to a failure state. */
3106 match = inverse;
3107
3108 switch (expect->type) {
3109 case TCPCHK_EXPECT_HTTP_STATUS:
3110 match = my_memmem(b_peek(&check->bi, 9), 3, expect->data.ptr, istlen(expect->data)) != NULL;
3111
3112 /* Set status and description in case of error */
3113 status = HCHK_STATUS_L7STS;
3114 desc = ist2(b_peek(&check->bi, 12), my_memcspn(b_peek(&check->bi, 12), b_data(&check->bi) - 12, "\r\n", 2));
3115 break;
3116 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
3117 match = regex_exec2(expect->regex, b_peek(&check->bi, 9), 3);
3118
3119 /* Set status and description in case of error */
3120 status = HCHK_STATUS_L7STS;
3121 desc = ist2(b_peek(&check->bi, 12), my_memcspn(b_peek(&check->bi, 12), b_data(&check->bi) - 12, "\r\n", 2));
3122 break;
3123 case TCPCHK_EXPECT_HTTP_BODY:
3124 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
3125 body = (char *)my_memmem(b_head(&check->bi), b_data(&check->bi), "\r\n\r\n", 4);
3126 if (!body) {
3127 if (!last_read)
3128 goto wait_more_data;
3129
3130 status = HCHK_STATUS_L7RSP;
3131 desc = ist("HTTP content check could not find a response body");
3132 goto error;
3133 }
3134 body += 4;
3135 body_len = b_tail(&check->bi) - body;
3136
3137 if (!last_read &&
3138 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && body_len < istlen(expect->data)) ||
3139 (expect->min_recv > 0 && body_len < expect->min_recv))) {
3140 ret = TCPCHK_EVAL_WAIT;
3141 goto out;
3142 }
3143
3144 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
3145 match = my_memmem(body, body_len, expect->data.ptr, istlen(expect->data)) != NULL;
3146 else
3147 match = regex_exec2(expect->regex, body, body_len);
3148
3149 /* Set status and description in case of error */
3150 status = HCHK_STATUS_L7RSP;
3151 desc = (inverse
3152 ? ist("HTTP check matched unwanted content")
3153 : ist("HTTP content check did not match"));
3154 break;
3155
3156 default:
3157 /* should never happen */
3158 status = HCHK_STATUS_L7RSP;
3159 goto error;
3160 }
3161
3162 /* Wait for more data on mismatch only if no minimum is defined (-1),
3163 * otherwise the absence of match is already conclusive.
3164 */
3165 if (!match && !last_read && (expect->min_recv == -1)) {
3166 ret = TCPCHK_EVAL_WAIT;
3167 goto out;
3168 }
3169
3170 if (!(match ^ inverse))
3171 goto error;
3172
3173 out:
3174 free_trash_chunk(msg);
3175 return ret;
3176
3177 error:
3178 ret = TCPCHK_EVAL_STOP;
3179 msg = alloc_trash_chunk();
3180 if (msg)
3181 tcpcheck_onerror_message(msg, check, rule, 0, desc);
3182 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003183 goto out;
3184
Christopher Faulete5870d82020-04-15 11:32:03 +02003185 wait_more_data:
3186 ret = TCPCHK_EVAL_WAIT;
3187 goto out;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003188}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01003189
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003190/* Evaluate a TCPCHK_ACT_EXPECT rule. It returns 1 to evaluate the next rule, 0
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003191 * to wait and -1 to stop the check.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003192 */
3193static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
3194{
3195 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Fauletec07e382020-04-07 14:56:26 +02003196 struct tcpcheck_expect *expect = &rule->expect;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003197 struct buffer *msg = NULL;
Christopher Faulet12d57402020-04-10 09:58:42 +02003198 int match, inverse;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003199
Christopher Faulete5870d82020-04-15 11:32:03 +02003200 last_read |= b_full(&check->bi);
3201
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003202 /* The current expect might need more data than the previous one, check again
3203 * that the minimum amount data required to match is respected.
3204 */
3205 if (!last_read) {
3206 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003207 (b_data(&check->bi) < istlen(expect->data))) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003208 ret = TCPCHK_EVAL_WAIT;
3209 goto out;
3210 }
3211 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
3212 ret = TCPCHK_EVAL_WAIT;
3213 goto out;
3214 }
3215 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003216
Christopher Faulet12d57402020-04-10 09:58:42 +02003217 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003218 /* Make GCC happy ; initialize match to a failure state. */
Christopher Faulet12d57402020-04-10 09:58:42 +02003219 match = inverse;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003220
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003221 switch (expect->type) {
3222 case TCPCHK_EXPECT_STRING:
3223 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003224 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 +02003225 break;
3226 case TCPCHK_EXPECT_REGEX:
Christopher Faulet12d57402020-04-10 09:58:42 +02003227 if (expect->flags & TCPCHK_EXPT_FL_CAP)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003228 match = regex_exec_match2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1),
3229 MAX_MATCH, pmatch, 0);
3230 else
3231 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
3232 break;
Willy Tarreau7df8ca62019-07-15 10:57:51 +02003233
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003234 case TCPCHK_EXPECT_REGEX_BINARY:
3235 chunk_reset(&trash);
3236 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet12d57402020-04-10 09:58:42 +02003237 if (expect->flags & TCPCHK_EXPT_FL_CAP)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003238 match = regex_exec_match2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1),
3239 MAX_MATCH, pmatch, 0);
3240 else
3241 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
3242 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003243 case TCPCHK_EXPECT_CUSTOM:
3244 if (expect->custom)
3245 ret = expect->custom(check, rule, last_read);
3246 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02003247 default:
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003248 /* Should never happen. */
3249 ret = TCPCHK_EVAL_STOP;
3250 goto out;
3251 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003252
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003253
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003254 /* Wait for more data on mismatch only if no minimum is defined (-1),
3255 * otherwise the absence of match is already conclusive.
3256 */
3257 if (!match && !last_read && (expect->min_recv == -1)) {
3258 ret = TCPCHK_EVAL_WAIT;
3259 goto out;
3260 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003261
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003262 /* Result as expected, next rule. */
Christopher Faulet12d57402020-04-10 09:58:42 +02003263 if (match ^ inverse)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003264 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003265
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01003266
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003267 /* From this point on, we matched something we did not want, this is an error state. */
3268 ret = TCPCHK_EVAL_STOP;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003269 msg = alloc_trash_chunk();
Christopher Faulet206368d2020-04-03 14:51:06 +02003270 if (msg)
3271 tcpcheck_onerror_message(msg, check, rule, match, ist(NULL));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003272 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
Christopher Faulet206368d2020-04-03 14:51:06 +02003273 free_trash_chunk(msg);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003274 ret = TCPCHK_EVAL_STOP;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003275
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003276 out:
3277 return ret;
3278}
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003279
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003280/* Evaluate a TCPCHK_ACT_ACTION_KW rule. It returns 1 to evaluate the next rule, 0
3281 * to wait and -1 to stop the check.
3282 */
3283static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
3284{
3285 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3286 struct act_rule *act_rule;
3287 enum act_return act_ret;
3288
3289 act_rule =rule->action_kw.rule;
3290 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
3291 if (act_ret != ACT_RET_CONT) {
3292 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
3293 tcpcheck_get_step_id(check, rule));
3294 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3295 ret = TCPCHK_EVAL_STOP;
3296 }
3297
3298 return ret;
3299}
3300
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003301/* proceed with next steps for the TCP checks <check>. Note that this is called
3302 * both from the connection's wake() callback and from the check scheduling task.
3303 * It returns 0 on normal cases, or <0 if a close() has happened on an existing
3304 * connection, presenting the risk of an fd replacement.
3305 *
3306 * Please do NOT place any return statement in this function and only leave
3307 * via the out_end_tcpcheck label after setting retcode.
3308 */
3309static int tcpcheck_main(struct check *check)
3310{
3311 struct tcpcheck_rule *rule;
3312 struct conn_stream *cs = check->cs;
3313 struct connection *conn = cs_conn(cs);
3314 int must_read = 1, last_read = 0;
3315 int ret, retcode = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003316
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003317 /* here, we know that the check is complete or that it failed */
3318 if (check->result != CHK_RES_UNKNOWN)
3319 goto out_end_tcpcheck;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003320
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003321 /* 1- check for connection error, if any */
3322 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3323 goto out_end_tcpcheck;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003324
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003325 /* 2- check if we are waiting for the connection establishment. It only
3326 * happens during TCPCHK_ACT_CONNECT. */
Christopher Faulet370e0f12020-04-16 09:52:42 +02003327 if (check->current_step && check->current_step->action == TCPCHK_ACT_CONNECT) {
3328 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
3329 if (conn && (conn->flags & CO_FL_WAIT_XPRT)) {
3330 if (rule->action == TCPCHK_ACT_SEND)
3331 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3332 else if (rule->action == TCPCHK_ACT_EXPECT)
3333 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003334 goto out;
3335 }
Christopher Faulet370e0f12020-04-16 09:52:42 +02003336 }
3337
3338 /* 3- check for pending outgoing data. It only happens during
3339 * TCPCHK_ACT_SEND. */
3340 else if (check->current_step && check->current_step->action == TCPCHK_ACT_SEND) {
3341 if (conn && b_data(&check->bo)) {
3342 ret = conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
3343 if (ret <= 0) {
3344 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3345 goto out_end_tcpcheck;
3346 goto out;
3347 }
3348 if (b_data(&check->bo)) {
3349 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3350 goto out;
3351 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003352 }
Christopher Faulet370e0f12020-04-16 09:52:42 +02003353 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003354 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003355
Christopher Faulet370e0f12020-04-16 09:52:42 +02003356 /* 4- check if a rule must be resume. It happens if check->current_step
3357 * is defined. */
3358 else if (check->current_step)
3359 rule = check->current_step;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003360
Christopher Faulet370e0f12020-04-16 09:52:42 +02003361 /* 5- It is the first evaluation. We must create a session and preset
3362 * tcp-check variables */
3363 else {
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003364 struct tcpcheck_var *var;
3365
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003366 /* First evaluation, create a session */
Christopher Faulet0fca7ed2020-04-21 11:53:32 +02003367 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003368 if (!check->sess) {
3369 chunk_printf(&trash, "TCPCHK error allocating check session");
3370 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3371 goto out_end_tcpcheck;
3372 }
Gaetan Rivet13a50432020-02-21 18:13:44 +01003373 vars_init(&check->vars, SCOPE_CHECK);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003374 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003375
3376 /* Preset tcp-check variables */
3377 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
3378 struct sample smp;
3379
3380 memset(&smp, 0, sizeof(smp));
3381 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
3382 smp.data = var->data;
3383 vars_set_by_name_ifexist(var->name.ptr, var->name.len, &smp);
3384 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003385 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003386
Christopher Faulet370e0f12020-04-16 09:52:42 +02003387 /* Now evaluate the tcp-check rules */
3388
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003389 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003390 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003391
Christopher Faulete5870d82020-04-15 11:32:03 +02003392 check->code = 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003393 switch (rule->action) {
3394 case TCPCHK_ACT_CONNECT:
3395 check->current_step = rule;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003396
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003397 /* close but not release yet previous connection */
3398 if (check->cs) {
3399 cs_close(check->cs);
3400 retcode = -1; /* do not reuse the fd in the caller! */
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003401 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003402 eval_ret = tcpcheck_eval_connect(check, rule);
3403 must_read = 1; last_read = 0;
3404 break;
3405 case TCPCHK_ACT_SEND:
3406 check->current_step = rule;
3407 eval_ret = tcpcheck_eval_send(check, rule);
3408 must_read = 1;
3409 break;
3410 case TCPCHK_ACT_EXPECT:
3411 check->current_step = rule;
3412 if (must_read) {
3413 if (check->proxy->timeout.check)
3414 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003415
Christopher Fauletf9585d82020-04-16 13:25:58 +02003416 eval_ret = tcpcheck_eval_recv(check, rule);
3417 if (eval_ret == TCPCHK_EVAL_STOP)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003418 goto out_end_tcpcheck;
Christopher Fauletf9585d82020-04-16 13:25:58 +02003419 else if (eval_ret == TCPCHK_EVAL_WAIT)
3420 goto out;
3421 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003422 must_read = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003423 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003424
Christopher Faulete5870d82020-04-15 11:32:03 +02003425 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
3426 ? tcpcheck_eval_expect_http(check, rule, last_read)
3427 : tcpcheck_eval_expect(check, rule, last_read));
3428
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003429 if (eval_ret == TCPCHK_EVAL_WAIT) {
3430 check->current_step = rule->expect.head;
3431 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003432 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003433 break;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003434 case TCPCHK_ACT_ACTION_KW:
3435 /* Don't update the current step */
3436 eval_ret = tcpcheck_eval_action_kw(check, rule);
3437 break;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003438 default:
3439 /* Otherwise, just go to the next one and don't update
3440 * the current step
3441 */
3442 eval_ret = TCPCHK_EVAL_CONTINUE;
3443 break;
3444 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003445
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003446 switch (eval_ret) {
3447 case TCPCHK_EVAL_CONTINUE:
3448 break;
3449 case TCPCHK_EVAL_WAIT:
3450 goto out;
3451 case TCPCHK_EVAL_STOP:
3452 goto out_end_tcpcheck;
Baptiste Assmann248f1172018-03-01 21:49:01 +01003453 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003454 }
3455
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003456 /* All rules was evaluated */
Christopher Fauletdf38f882020-04-07 16:04:38 +02003457 if (check->current_step) {
3458 rule = check->current_step;
3459
3460 if (rule->action == TCPCHK_ACT_EXPECT) {
Christopher Faulete5870d82020-04-15 11:32:03 +02003461 struct buffer *msg;
3462
3463 if (check->server &&
3464 (check->server->proxy->options & PR_O_DISABLE404) &&
3465 (check->server->next_state != SRV_ST_STOPPED) &&
3466 (check->code == 404)) {
3467 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
3468 goto out_end_tcpcheck;
3469 }
Christopher Fauletdf38f882020-04-07 16:04:38 +02003470
Christopher Faulete5870d82020-04-15 11:32:03 +02003471 msg = alloc_trash_chunk();
Christopher Fauletdf38f882020-04-07 16:04:38 +02003472 if (msg)
3473 tcpcheck_onsuccess_message(msg, check, rule, ist(NULL));
3474 set_server_check_status(check, rule->expect.ok_status,
3475 (msg ? b_head(msg) : "(tcp-check)"));
3476 free_trash_chunk(msg);
3477 }
3478 else if (rule->action == TCPCHK_ACT_CONNECT) {
3479 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
3480 enum healthcheck_status status = ((conn && ssl_sock_is_ssl(conn)) ? HCHK_STATUS_L6OK : HCHK_STATUS_L4OK);
3481
3482 set_server_check_status(check, status, msg);
3483 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003484 }
Christopher Fauletec07e382020-04-07 14:56:26 +02003485 else
3486 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003487
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003488 out_end_tcpcheck:
Willy Tarreauef91c932019-07-23 14:37:47 +02003489 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003490 chk_report_conn_err(check, errno, 0);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003491
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003492 /* cleanup before leaving */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003493 check->current_step = NULL;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003494 if (check->sess != NULL) {
Gaetan Rivet13a50432020-02-21 18:13:44 +01003495 vars_prune(&check->vars, check->sess, NULL);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003496 session_free(check->sess);
3497 check->sess = NULL;
3498 }
3499 out:
3500 return retcode;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003501}
3502
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003503static const char *init_check(struct check *check, int type)
Simon Hormanb1900d52015-01-30 11:22:54 +09003504{
3505 check->type = type;
3506
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003507 b_reset(&check->bi); check->bi.size = global.tune.chksize;
3508 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Simon Hormanb1900d52015-01-30 11:22:54 +09003509
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003510 check->bi.area = calloc(check->bi.size, sizeof(char));
3511 check->bo.area = calloc(check->bo.size, sizeof(char));
3512
3513 if (!check->bi.area || !check->bo.area)
Simon Hormanb1900d52015-01-30 11:22:54 +09003514 return "out of memory while allocating check buffer";
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003515
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003516 check->wait_list.tasklet = tasklet_new();
3517 if (!check->wait_list.tasklet)
Ilya Shipitsind4259502020-04-08 01:07:56 +05003518 return "out of memory while allocating check tasklet";
Willy Tarreau4f6516d2018-12-19 13:59:17 +01003519 check->wait_list.events = 0;
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003520 check->wait_list.tasklet->process = event_srv_chk_io;
3521 check->wait_list.tasklet->context = check;
Simon Hormanb1900d52015-01-30 11:22:54 +09003522 return NULL;
3523}
3524
Simon Hormanbfb5d332015-01-30 11:22:55 +09003525void free_check(struct check *check)
3526{
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003527 task_destroy(check->task);
3528 if (check->wait_list.tasklet)
3529 tasklet_free(check->wait_list.tasklet);
3530
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003531 free(check->bi.area);
3532 free(check->bo.area);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003533 if (check->cs) {
3534 free(check->cs->conn);
3535 check->cs->conn = NULL;
3536 cs_free(check->cs);
3537 check->cs = NULL;
3538 }
Simon Hormanbfb5d332015-01-30 11:22:55 +09003539}
3540
Christopher Faulete5870d82020-04-15 11:32:03 +02003541static void free_tcpcheck_fmt(struct list *fmt)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003542{
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003543 struct logformat_node *lf, *lfb;
3544
Christopher Faulete5870d82020-04-15 11:32:03 +02003545 list_for_each_entry_safe(lf, lfb, fmt, list) {
3546 LIST_DEL(&lf->list);
3547 release_sample_expr(lf->expr);
3548 free(lf->arg);
3549 free(lf);
3550 }
3551}
3552
3553static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
3554{
3555 if (!hdr)
3556 return;
3557
3558 free_tcpcheck_fmt(&hdr->value);
3559 free(hdr->name.ptr);
3560 free(hdr);
3561}
3562
3563static void free_tcpcheck_http_hdrs(struct list *hdrs)
3564{
3565 struct tcpcheck_http_hdr *hdr, *bhdr;
3566
3567 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
3568 LIST_DEL(&hdr->list);
3569 free_tcpcheck_http_hdr(hdr);
3570 }
3571}
3572
3573static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
3574{
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003575 if (!rule)
3576 return;
3577
3578 free(rule->comment);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003579 switch (rule->action) {
3580 case TCPCHK_ACT_SEND:
3581 switch (rule->send.type) {
3582 case TCPCHK_SEND_STRING:
3583 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003584 free(rule->send.data.ptr);
3585 break;
3586 case TCPCHK_SEND_STRING_LF:
3587 case TCPCHK_SEND_BINARY_LF:
Christopher Faulete5870d82020-04-15 11:32:03 +02003588 free_tcpcheck_fmt(&rule->send.fmt);
3589 break;
3590 case TCPCHK_SEND_HTTP:
3591 free(rule->send.http.meth.str.area);
3592 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3593 free(rule->send.http.uri.ptr);
3594 else
3595 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
3596 free(rule->send.http.vsn.ptr);
3597 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
3598 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3599 free(rule->send.http.body.ptr);
3600 else
3601 free_tcpcheck_fmt(&rule->send.http.body_fmt);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003602 break;
3603 case TCPCHK_SEND_UNDEF:
3604 break;
3605 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003606 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003607 case TCPCHK_ACT_EXPECT:
Christopher Faulete5870d82020-04-15 11:32:03 +02003608 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
3609 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003610 release_sample_expr(rule->expect.status_expr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003611 switch (rule->expect.type) {
3612 case TCPCHK_EXPECT_STRING:
3613 case TCPCHK_EXPECT_BINARY:
Christopher Faulete5870d82020-04-15 11:32:03 +02003614 case TCPCHK_EXPECT_HTTP_STATUS:
3615 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003616 free(rule->expect.data.ptr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003617 break;
3618 case TCPCHK_EXPECT_REGEX:
3619 case TCPCHK_EXPECT_REGEX_BINARY:
Christopher Faulete5870d82020-04-15 11:32:03 +02003620 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
3621 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003622 regex_free(rule->expect.regex);
3623 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003624 case TCPCHK_EXPECT_CUSTOM:
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003625 case TCPCHK_EXPECT_UNDEF:
3626 break;
3627 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003628 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003629 case TCPCHK_ACT_CONNECT:
Christopher Faulet79b31d42020-03-30 13:00:05 +02003630 free(rule->connect.sni);
Christopher Faulet98572322020-03-30 13:16:44 +02003631 free(rule->connect.alpn);
Christopher Fauletb7d30092020-03-30 15:19:03 +02003632 release_sample_expr(rule->connect.port_expr);
Christopher Faulet79b31d42020-03-30 13:00:05 +02003633 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003634 case TCPCHK_ACT_COMMENT:
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003635 break;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01003636 case TCPCHK_ACT_ACTION_KW:
3637 free(rule->action_kw.rule);
3638 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003639 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003640
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003641 if (in_pool)
3642 pool_free(pool_head_tcpcheck_rule, rule);
3643 else
3644 free(rule);
3645}
3646
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003647
Christopher Fauletce355072020-04-02 11:44:39 +02003648static struct tcpcheck_var *tcpcheck_var_create(const char *name)
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003649{
3650 struct tcpcheck_var *var = NULL;
3651
3652 var = calloc(1, sizeof(*var));
3653 if (var == NULL)
3654 return NULL;
3655
3656 var->name = ist2(strdup(name), strlen(name));
3657 if (var->name.ptr == NULL) {
3658 free(var);
3659 return NULL;
3660 }
3661
3662 LIST_INIT(&var->list);
3663 return var;
3664}
3665
3666static void tcpcheck_var_release(struct tcpcheck_var *var)
3667{
3668 if (!var)
3669 return;
3670
3671 free(var->name.ptr);
3672 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
3673 free(var->data.u.str.area);
3674 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
3675 free(var->data.u.meth.str.area);
3676 free(var);
3677}
3678
3679int dup_tcpcheck_vars(struct list *dst, struct list *src)
3680{
3681 struct tcpcheck_var *var, *new = NULL;
3682
3683 list_for_each_entry(var, src, list) {
3684 new = tcpcheck_var_create(var->name.ptr);
3685 if (!new)
3686 goto error;
3687 new->data.type = var->data.type;
3688 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
3689 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3690 goto error;
3691 if (var->data.type == SMP_T_STR)
3692 new->data.u.str.area[new->data.u.str.data] = 0;
3693 }
3694 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
3695 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3696 goto error;
3697 new->data.u.str.area[new->data.u.str.data] = 0;
3698 new->data.u.meth.meth = var->data.u.meth.meth;
3699 }
3700 else
3701 new->data.u = var->data.u;
3702 LIST_ADDQ(dst, &new->list);
3703 }
3704 return 1;
3705
3706 error:
3707 free(new);
3708 return 0;
3709}
3710
3711static void free_tcpcheck_vars(struct list *vars)
3712{
3713 struct tcpcheck_var *var, *back;
3714
3715 list_for_each_entry_safe(var, back, vars, list) {
3716 LIST_DEL(&var->list);
3717 tcpcheck_var_release(var);
3718 }
3719}
3720
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003721void email_alert_free(struct email_alert *alert)
3722{
3723 struct tcpcheck_rule *rule, *back;
3724
3725 if (!alert)
3726 return;
3727
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003728 if (alert->rules.list) {
3729 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
3730 LIST_DEL(&rule->list);
3731 free_tcpcheck(rule, 1);
3732 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003733 free_tcpcheck_vars(&alert->rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003734 free(alert->rules.list);
3735 alert->rules.list = NULL;
Christopher Fauletde1a75b2017-10-23 15:38:19 +02003736 }
Willy Tarreaubafbe012017-11-24 17:34:44 +01003737 pool_free(pool_head_email_alert, alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003738}
3739
Olivier Houchard9f6af332018-05-25 14:04:04 +02003740static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003741{
Olivier Houchard9f6af332018-05-25 14:04:04 +02003742 struct check *check = context;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003743 struct email_alertq *q;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003744 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003745
3746 q = container_of(check, typeof(*q), check);
3747
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003748 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003749 while (1) {
3750 if (!(check->state & CHK_ST_ENABLED)) {
3751 if (LIST_ISEMPTY(&q->email_alerts)) {
3752 /* All alerts processed, queue the task */
3753 t->expire = TICK_ETERNITY;
3754 task_queue(t);
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003755 goto end;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003756 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003757
3758 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003759 LIST_DEL(&alert->list);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003760 t->expire = now_ms;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003761 check->tcpcheck_rules = &alert->rules;
Olivier Houchard0923fa42019-01-11 18:43:04 +01003762 check->status = HCHK_STATUS_INI;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003763 check->state |= CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003764 }
3765
Olivier Houchard9f6af332018-05-25 14:04:04 +02003766 process_chk(t, context, state);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003767 if (check->state & CHK_ST_INPROGRESS)
3768 break;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003769
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003770 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003771 email_alert_free(alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003772 check->tcpcheck_rules = NULL;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003773 check->server = NULL;
3774 check->state &= ~CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003775 }
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003776 end:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003777 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003778 return t;
3779}
3780
Christopher Faulet0108bb32017-10-20 21:34:32 +02003781/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
3782 *
3783 * The function returns 1 in success case, otherwise, it returns 0 and err is
3784 * filled.
3785 */
3786int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003787{
Christopher Faulet0108bb32017-10-20 21:34:32 +02003788 struct mailer *mailer;
3789 struct email_alertq *queues;
3790 const char *err_str;
3791 int i = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003792
Christopher Faulet0108bb32017-10-20 21:34:32 +02003793 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
3794 memprintf(err, "out of memory while allocating mailer alerts queues");
mildis5ab01cb2018-10-02 16:46:34 +02003795 goto fail_no_queue;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003796 }
3797
Christopher Faulet0108bb32017-10-20 21:34:32 +02003798 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
3799 struct email_alertq *q = &queues[i];
3800 struct check *check = &q->check;
3801 struct task *t;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003802
3803 LIST_INIT(&q->email_alerts);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003804 HA_SPIN_INIT(&q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003805 check->inter = mls->timeout.mail;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003806 check->rise = DEF_AGENT_RISETIME;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01003807 check->proxy = p;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003808 check->fall = DEF_AGENT_FALLTIME;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003809 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
3810 memprintf(err, "%s", err_str);
3811 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003812 }
3813
3814 check->xprt = mailer->xprt;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003815 check->addr = mailer->addr;
Christopher Fauletb797ae12018-03-27 15:35:35 +02003816 check->port = get_host_port(&mailer->addr);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003817
Emeric Brunc60def82017-09-27 14:59:38 +02003818 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003819 memprintf(err, "out of memory while allocating mailer alerts task");
3820 goto error;
3821 }
3822
3823 check->task = t;
3824 t->process = process_email_alert;
3825 t->context = check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003826
Christopher Faulet0108bb32017-10-20 21:34:32 +02003827 /* check this in one ms */
3828 t->expire = TICK_ETERNITY;
3829 check->start = now;
3830 task_queue(t);
3831 }
3832
3833 mls->users++;
3834 free(p->email_alert.mailers.name);
3835 p->email_alert.mailers.m = mls;
3836 p->email_alert.queues = queues;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003837 return 0;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003838
3839 error:
3840 for (i = 0; i < mls->count; i++) {
3841 struct email_alertq *q = &queues[i];
3842 struct check *check = &q->check;
3843
Christopher Faulet0108bb32017-10-20 21:34:32 +02003844 free_check(check);
3845 }
3846 free(queues);
mildis5ab01cb2018-10-02 16:46:34 +02003847 fail_no_queue:
Christopher Faulet0108bb32017-10-20 21:34:32 +02003848 return 1;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003849}
3850
3851
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003852static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003853{
Gaetan Rivet4038b942020-02-26 16:19:40 +01003854 struct tcpcheck_rule *tcpcheck, *prev_check;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003855 struct tcpcheck_expect *expect;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003856
Willy Tarreaubafbe012017-11-24 17:34:44 +01003857 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003858 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003859 memset(tcpcheck, 0, sizeof(*tcpcheck));
Gaetan Rivetb616add2020-02-07 15:37:17 +01003860 tcpcheck->action = TCPCHK_ACT_EXPECT;
3861
3862 expect = &tcpcheck->expect;
3863 expect->type = TCPCHK_EXPECT_STRING;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003864 LIST_INIT(&expect->onerror_fmt);
3865 LIST_INIT(&expect->onsuccess_fmt);
Christopher Fauletec07e382020-04-07 14:56:26 +02003866 expect->ok_status = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003867 expect->err_status = HCHK_STATUS_L7RSP;
3868 expect->tout_status = HCHK_STATUS_L7TOUT;
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003869 expect->data = ist2(strdup(str), strlen(str));
3870 if (!expect->data.ptr) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003871 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003872 return 0;
3873 }
3874
Gaetan Rivet4038b942020-02-26 16:19:40 +01003875 /* All tcp-check expect points back to the first inverse expect rule
3876 * in a chain of one or more expect rule, potentially itself.
3877 */
Gaetan Rivetb616add2020-02-07 15:37:17 +01003878 tcpcheck->expect.head = tcpcheck;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003879 list_for_each_entry_rev(prev_check, rules->list, list) {
Gaetan Rivet4038b942020-02-26 16:19:40 +01003880 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet12d57402020-04-10 09:58:42 +02003881 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
Gaetan Rivetb616add2020-02-07 15:37:17 +01003882 tcpcheck->expect.head = prev_check;
Gaetan Rivet4038b942020-02-26 16:19:40 +01003883 continue;
3884 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003885 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet4038b942020-02-26 16:19:40 +01003886 break;
3887 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003888 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003889 return 1;
3890}
3891
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003892static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003893{
3894 struct tcpcheck_rule *tcpcheck;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003895 struct tcpcheck_send *send;
Willy Tarreau64345aa2016-08-10 19:29:09 +02003896 const char *in;
3897 char *dst;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003898 int i;
3899
Willy Tarreaubafbe012017-11-24 17:34:44 +01003900 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003901 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003902 memset(tcpcheck, 0, sizeof(*tcpcheck));
3903 tcpcheck->action = TCPCHK_ACT_SEND;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003904
3905 send = &tcpcheck->send;
3906 send->type = TCPCHK_SEND_STRING;
3907
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003908 for (i = 0; strs[i]; i++)
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003909 send->data.len += strlen(strs[i]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003910
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003911 send->data.ptr = malloc(send->data.len + 1);
3912 if (!isttest(send->data)) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003913 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003914 return 0;
3915 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003916
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003917 dst = send->data.ptr;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003918 for (i = 0; strs[i]; i++)
Willy Tarreau64345aa2016-08-10 19:29:09 +02003919 for (in = strs[i]; (*dst = *in++); dst++);
3920 *dst = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003921
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003922 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003923 return 1;
3924}
3925
Christopher Faulet0108bb32017-10-20 21:34:32 +02003926static int enqueue_one_email_alert(struct proxy *p, struct server *s,
3927 struct email_alertq *q, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003928{
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003929 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003930 struct tcpcheck_rule *tcpcheck;
3931 struct check *check = &q->check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003932
Willy Tarreaubafbe012017-11-24 17:34:44 +01003933 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003934 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003935 LIST_INIT(&alert->list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003936 alert->rules.flags = 0;
3937 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
3938 if (!alert->rules.list)
3939 goto error;
3940 LIST_INIT(alert->rules.list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003941 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
Christopher Faulet0108bb32017-10-20 21:34:32 +02003942 alert->srv = s;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003943
Willy Tarreaubafbe012017-11-24 17:34:44 +01003944 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003945 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003946 memset(tcpcheck, 0, sizeof(*tcpcheck));
3947 tcpcheck->action = TCPCHK_ACT_CONNECT;
3948 tcpcheck->comment = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003949
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003950 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003951
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003952 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003953 goto error;
3954
3955 {
3956 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003957 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003958 goto error;
3959 }
3960
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003961 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003962 goto error;
3963
3964 {
3965 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003966 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003967 goto error;
3968 }
3969
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003970 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003971 goto error;
3972
3973 {
3974 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003975 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003976 goto error;
3977 }
3978
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003979 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003980 goto error;
3981
3982 {
3983 const char * const strs[2] = { "DATA\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003984 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003985 goto error;
3986 }
3987
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003988 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003989 goto error;
3990
3991 {
3992 struct tm tm;
3993 char datestr[48];
3994 const char * const strs[18] = {
Pieter Baauw5e0964e2016-02-13 16:27:35 +01003995 "From: ", p->email_alert.from, "\r\n",
3996 "To: ", p->email_alert.to, "\r\n",
3997 "Date: ", datestr, "\r\n",
3998 "Subject: [HAproxy Alert] ", msg, "\r\n",
3999 "\r\n",
4000 msg, "\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004001 "\r\n",
Pieter Baauwed35c372015-07-22 19:51:54 +02004002 ".\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004003 NULL
4004 };
4005
4006 get_localtime(date.tv_sec, &tm);
4007
4008 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
4009 goto error;
4010 }
4011
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004012 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004013 goto error;
4014 }
4015
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004016 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004017 goto error;
4018
4019 {
4020 const char * const strs[2] = { "QUIT\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004021 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004022 goto error;
4023 }
4024
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004025 if (!add_tcpcheck_expect_str(&alert->rules, "221 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004026 goto error;
4027
Christopher Faulet2a944ee2017-11-07 10:42:54 +01004028 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02004029 task_wakeup(check->task, TASK_WOKEN_MSG);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004030 LIST_ADDQ(&q->email_alerts, &alert->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01004031 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004032 return 1;
4033
4034error:
4035 email_alert_free(alert);
4036 return 0;
4037}
4038
Christopher Faulet0108bb32017-10-20 21:34:32 +02004039static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004040{
4041 int i;
4042 struct mailer *mailer;
4043
4044 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
4045 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02004046 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01004047 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004048 return;
4049 }
4050 }
4051
4052 return;
4053}
4054
4055/*
4056 * Send email alert if configured.
4057 */
Simon Horman64e34162015-02-06 11:11:57 +09004058void send_email_alert(struct server *s, int level, const char *format, ...)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004059{
4060 va_list argp;
4061 char buf[1024];
4062 int len;
4063 struct proxy *p = s->proxy;
4064
Christopher Faulet0108bb32017-10-20 21:34:32 +02004065 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004066 return;
4067
4068 va_start(argp, format);
4069 len = vsnprintf(buf, sizeof(buf), format, argp);
4070 va_end(argp);
4071
Thierry FOURNIER62c8a212017-02-09 12:19:27 +01004072 if (len < 0 || len >= sizeof(buf)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01004073 ha_alert("Email alert [%s] could not format message\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004074 return;
4075 }
4076
Christopher Faulet0108bb32017-10-20 21:34:32 +02004077 enqueue_email_alert(p, s, buf);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004078}
4079
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004080/*
4081 * Return value:
4082 * the port to be used for the health check
4083 * 0 in case no port could be found for the check
4084 */
Christopher Faulet31c30fd2020-03-26 21:10:03 +01004085static int srv_check_healthcheck_port(struct check *chk)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004086{
4087 int i = 0;
4088 struct server *srv = NULL;
4089
4090 srv = chk->server;
4091
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004092 /* by default, we use the health check port ocnfigured */
4093 if (chk->port > 0)
4094 return chk->port;
4095
4096 /* try to get the port from check_core.addr if check.port not set */
4097 i = get_host_port(&chk->addr);
4098 if (i > 0)
4099 return i;
4100
4101 /* try to get the port from server address */
4102 /* prevent MAPPORTS from working at this point, since checks could
4103 * not be performed in such case (MAPPORTS impose a relative ports
4104 * based on live traffic)
4105 */
4106 if (srv->flags & SRV_F_MAPPORTS)
4107 return 0;
Willy Tarreau04276f32017-01-06 17:41:29 +01004108
4109 i = srv->svc_port; /* by default */
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004110 if (i > 0)
4111 return i;
4112
4113 return 0;
4114}
4115
Willy Tarreau172f5ce2018-11-26 11:21:50 +01004116REGISTER_POST_CHECK(start_checks);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02004117
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004118static int check_proxy_tcpcheck(struct proxy *px)
4119{
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004120 struct tcpcheck_rule *chk, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02004121 char *comment = NULL, *errmsg = NULL;
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004122 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004123 int ret = 0;
4124
Christopher Faulete5870d82020-04-15 11:32:03 +02004125 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
4126 deinit_proxy_tcpcheck(px);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004127 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004128 }
4129
4130 free(px->check_command);
4131 free(px->check_path);
4132 px->check_command = px->check_path = NULL;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004133
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004134 if (!px->tcpcheck_rules.list) {
Christopher Faulet404f9192020-04-09 23:13:54 +02004135 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4136 ret |= ERR_ALERT | ERR_FATAL;
4137 goto out;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004138 }
4139
Christopher Faulete5870d82020-04-15 11:32:03 +02004140 /* HTTP ruleset */
4141 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4142 struct tcpcheck_rule *next;
4143
4144 /* move remaining send rule from "option httpchk" line to the right place */
4145 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4146 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4147 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
4148 if (next && next->action == TCPCHK_ACT_CONNECT) {
4149 LIST_DEL(&chk->list);
4150 LIST_ADD(&next->list, &chk->list);
4151 chk->index = next->index;
4152 }
4153 }
4154
4155 /* add implicit expect rule if the last one is a send. */
4156 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
4157 if (chk && chk->action == TCPCHK_ACT_SEND) {
4158 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "rstatus", "^[23]", ""},
4159 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
4160 px->conf.file, px->conf.line, &errmsg);
4161 if (!next) {
4162 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
4163 "(%s).\n", px->id, errmsg);
4164 free(errmsg);
4165 ret |= ERR_ALERT | ERR_FATAL;
4166 goto out;
4167 }
4168 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
4169 next->index = chk->index;
4170 }
4171 }
4172
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004173 /* If there is no connect rule preceeding all send / expect rules, an
4174 * implicit one is inserted before all others
4175 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004176 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004177 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4178 chk = calloc(1, sizeof(*chk));
4179 if (!chk) {
4180 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4181 "(out of memory).\n", px->id);
4182 ret |= ERR_ALERT | ERR_FATAL;
4183 goto out;
4184 }
4185 chk->action = TCPCHK_ACT_CONNECT;
Christopher Fauletbb591a12020-04-01 16:52:17 +02004186 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004187 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004188 }
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004189
4190 /* Now remove comment rules */
4191 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4192 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4193 free(comment);
4194 comment = NULL;
4195 }
4196
4197 prev_action = chk->action;
4198 switch (chk->action) {
4199 case TCPCHK_ACT_COMMENT:
4200 free(comment);
4201 comment = chk->comment;
4202 LIST_DEL(&chk->list);
4203 free(chk);
4204 break;
4205 case TCPCHK_ACT_CONNECT:
4206 if (!chk->comment && comment)
4207 chk->comment = strdup(comment);
4208 /* fall though */
4209 case TCPCHK_ACT_ACTION_KW:
4210 free(comment);
4211 comment = NULL;
4212 break;
4213 case TCPCHK_ACT_SEND:
4214 case TCPCHK_ACT_EXPECT:
4215 if (!chk->comment && comment)
4216 chk->comment = strdup(comment);
4217 break;
4218 }
4219 }
4220 free(comment);
4221 comment = NULL;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004222
4223 out:
4224 return ret;
4225}
4226
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004227static int init_srv_check(struct server *srv)
4228{
4229 const char *err;
4230 struct tcpcheck_rule *r;
4231 int ret = 0;
4232
4233 if (!srv->do_check)
4234 goto out;
4235
4236
4237 /* If neither a port nor an addr was specified and no check transport
4238 * layer is forced, then the transport layer used by the checks is the
4239 * same as for the production traffic. Otherwise we use raw_sock by
4240 * default, unless one is specified.
4241 */
4242 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4243 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4244 srv->check.use_ssl = srv->use_ssl;
4245 srv->check.xprt = srv->xprt;
4246 }
4247 else if (srv->check.use_ssl == 1)
4248 srv->check.xprt = xprt_get(XPRT_SSL);
4249
4250 srv->check.send_proxy |= (srv->pp_opts);
4251 }
4252
4253 /* validate <srv> server health-check settings */
4254
4255 /* We need at least a service port, a check port or the first tcp-check
4256 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4257 */
4258 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4259 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4260 goto init;
4261
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004262 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004263 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4264 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4265 ret |= ERR_ALERT | ERR_ABORT;
4266 goto out;
4267 }
4268
4269 /* search the first action (connect / send / expect) in the list */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004270 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
Christopher Faulet5c288742020-03-31 08:15:58 +02004271 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004272 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4273 "nor tcp_check rule 'connect' with port information.\n",
4274 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4275 ret |= ERR_ALERT | ERR_ABORT;
4276 goto out;
4277 }
4278
4279 /* scan the tcp-check ruleset to ensure a port has been configured */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004280 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
Christopher Faulet5c288742020-03-31 08:15:58 +02004281 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004282 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4283 "and a tcp_check rule 'connect' with no port information.\n",
4284 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4285 ret |= ERR_ALERT | ERR_ABORT;
4286 goto out;
4287 }
4288 }
4289
4290 init:
4291 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4292 if (err) {
4293 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4294 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4295 ret |= ERR_ALERT | ERR_ABORT;
4296 goto out;
4297 }
4298 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4299 global.maxsock++;
4300
4301 out:
4302 return ret;
4303}
4304
4305static int init_srv_agent_check(struct server *srv)
4306{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004307 struct tcpcheck_rule *chk;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004308 const char *err;
4309 int ret = 0;
4310
4311 if (!srv->do_agent)
4312 goto out;
4313
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004314 /* If there is no connect rule preceeding all send / expect rules, an
4315 * implicit one is inserted before all others.
4316 */
4317 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4318 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4319 chk = calloc(1, sizeof(*chk));
4320 if (!chk) {
4321 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4322 " to agent-check for server '%s' (out of memory).\n",
4323 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4324 ret |= ERR_ALERT | ERR_FATAL;
4325 goto out;
4326 }
4327 chk->action = TCPCHK_ACT_CONNECT;
4328 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4329 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
4330 }
4331
4332
4333 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004334 if (err) {
4335 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4336 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4337 ret |= ERR_ALERT | ERR_ABORT;
4338 goto out;
4339 }
4340
4341 if (!srv->agent.inter)
4342 srv->agent.inter = srv->check.inter;
4343
4344 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4345 global.maxsock++;
4346
4347 out:
4348 return ret;
4349}
4350
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004351void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004352{
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02004353 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004354 px->tcpcheck_rules.flags = 0;
4355 px->tcpcheck_rules.list = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004356}
4357
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004358static void deinit_srv_check(struct server *srv)
4359{
Christopher Fauletce8111e2020-04-06 15:04:11 +02004360 if (srv->check.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004361 free_check(&srv->check);
Christopher Fauletce8111e2020-04-06 15:04:11 +02004362 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
4363 srv->do_check = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004364}
4365
4366
4367static void deinit_srv_agent_check(struct server *srv)
4368{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004369 if (srv->agent.tcpcheck_rules) {
4370 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
4371 free(srv->agent.tcpcheck_rules);
4372 srv->agent.tcpcheck_rules = NULL;
4373 }
4374
4375 if (srv->agent.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004376 free_check(&srv->agent);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004377
4378 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
4379 srv->do_agent = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004380}
4381
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004382static void deinit_tcpchecks()
4383{
4384 struct tcpcheck_ruleset *rs, *rsb;
4385 struct tcpcheck_rule *r, *rb;
4386
4387 list_for_each_entry_safe(rs, rsb, &tcpchecks_list, list) {
4388 LIST_DEL(&rs->list);
4389 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4390 LIST_DEL(&r->list);
4391 free_tcpcheck(r, 0);
4392 }
4393 free(rs->name);
4394 free(rs);
4395 }
4396}
4397
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004398
4399REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004400REGISTER_POST_SERVER_CHECK(init_srv_check);
4401REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
4402
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004403REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004404REGISTER_SERVER_DEINIT(deinit_srv_check);
4405REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004406REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004407
Christopher Faulet404f9192020-04-09 23:13:54 +02004408static struct tcpcheck_ruleset *tcpcheck_ruleset_lookup(const char *name)
4409{
4410 struct tcpcheck_ruleset *rs;
4411
4412 list_for_each_entry(rs, &tcpchecks_list, list) {
4413 if (strcmp(rs->name, name) == 0)
4414 return rs;
4415 }
4416 return NULL;
4417}
4418
4419static struct tcpcheck_ruleset *tcpcheck_ruleset_create(const char *name)
4420{
4421 struct tcpcheck_ruleset *rs;
4422
4423 rs = calloc(1, sizeof(*rs));
4424 if (rs == NULL)
4425 return NULL;
4426
4427 rs->name = strdup(name);
4428 if (rs->name == NULL) {
4429 free(rs);
4430 return NULL;
4431 }
4432
4433 LIST_INIT(&rs->list);
4434 LIST_INIT(&rs->rules);
4435 LIST_ADDQ(&tcpchecks_list, &rs->list);
4436 return rs;
4437}
4438
4439static void tcpcheck_ruleset_release(struct tcpcheck_ruleset *rs)
4440{
4441 struct tcpcheck_rule *r, *rb;
4442 if (!rs)
4443 return;
4444
4445 LIST_DEL(&rs->list);
4446 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4447 LIST_DEL(&r->list);
4448 free_tcpcheck(r, 0);
4449 }
4450 free(rs->name);
4451 free(rs);
4452}
4453
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004454/* extracts check payload at a fixed position and length */
4455static int
4456smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
4457{
4458 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
4459 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
Christopher Faulet0fca7ed2020-04-21 11:53:32 +02004460 struct check *check = (smp->sess ? objt_check(smp->sess->origin) : NULL);
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004461 struct buffer *buf;
4462
Christopher Faulet0fca7ed2020-04-21 11:53:32 +02004463 if (!check)
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004464 return 0;
4465
Christopher Faulet0fca7ed2020-04-21 11:53:32 +02004466 buf = &check->bi;
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004467 if (buf_offset > b_data(buf))
4468 goto no_match;
4469 if (buf_offset + buf_size > b_data(buf))
4470 buf_size = 0;
4471
4472 /* init chunk as read only */
4473 smp->data.type = SMP_T_STR;
4474 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
4475 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
4476
4477 return 1;
4478
4479 no_match:
4480 smp->flags = 0;
4481 return 0;
4482}
4483
4484static struct sample_fetch_kw_list smp_kws = {ILH, {
4485 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
4486 { /* END */ },
4487}};
4488
4489INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
4490
4491
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004492struct action_kw_list tcp_check_keywords = {
4493 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
4494};
4495
4496/* Return the struct action_kw associated to a keyword */
4497static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
4498{
4499 return action_lookup(&tcp_check_keywords.list, kw);
4500}
4501
4502static void action_kw_tcp_check_build_list(struct buffer *chk)
4503{
4504 action_build_list(&tcp_check_keywords.list, chk);
4505}
4506
4507/* Create a tcp-check rule resulting from parsing a custom keyword. */
4508static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004509 struct list *rules, struct action_kw *kw,
4510 const char *file, int line, char **errmsg)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004511{
4512 struct tcpcheck_rule *chk = NULL;
4513 struct act_rule *actrule = NULL;
4514
4515 actrule = calloc(1, sizeof(*actrule));
4516 if (!actrule) {
4517 memprintf(errmsg, "out of memory");
4518 goto error;
4519 }
4520 actrule->kw = kw;
4521 actrule->from = ACT_F_TCP_CHK;
4522
4523 cur_arg++;
4524 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
4525 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
4526 goto error;
4527 }
4528
4529 chk = calloc(1, sizeof(*chk));
4530 if (!chk) {
4531 memprintf(errmsg, "out of memory");
4532 goto error;
4533 }
4534 chk->action = TCPCHK_ACT_ACTION_KW;
4535 chk->action_kw.rule = actrule;
4536 return chk;
4537
4538 error:
4539 free(actrule);
4540 return NULL;
4541}
4542
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004543static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Fauletb7d30092020-03-30 15:19:03 +02004544 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004545{
4546 struct tcpcheck_rule *chk = NULL;
Christopher Faulet5c288742020-03-31 08:15:58 +02004547 struct sockaddr_storage *sk = NULL;
Christopher Faulet98572322020-03-30 13:16:44 +02004548 char *comment = NULL, *sni = NULL, *alpn = NULL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004549 struct sample_expr *port_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004550 unsigned short conn_opts = 0;
4551 long port = 0;
Christopher Faulet98572322020-03-30 13:16:44 +02004552 int alpn_len = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004553
4554 list_for_each_entry(chk, rules, list) {
Christopher Faulete5870d82020-04-15 11:32:03 +02004555 if (chk->action == TCPCHK_ACT_CONNECT)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004556 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02004557 if (chk->action == TCPCHK_ACT_COMMENT ||
4558 chk->action == TCPCHK_ACT_ACTION_KW ||
4559 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4560 continue;
4561
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004562 memprintf(errmsg, "first step MUST also be a 'connect', "
4563 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
4564 "when there is a 'connect' step in the tcp-check ruleset");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004565 goto error;
4566 }
4567
4568 cur_arg++;
4569 while (*(args[cur_arg])) {
Christopher Fauletbb591a12020-04-01 16:52:17 +02004570 if (strcmp(args[cur_arg], "default") == 0)
4571 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
Christopher Faulet5c288742020-03-31 08:15:58 +02004572 else if (strcmp(args[cur_arg], "addr") == 0) {
4573 int port1, port2;
4574 struct protocol *proto;
4575
4576 if (!*(args[cur_arg+1])) {
4577 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
4578 goto error;
4579 }
4580
4581 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
4582 if (!sk) {
4583 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
4584 goto error;
4585 }
4586
4587 proto = protocol_by_family(sk->ss_family);
4588 if (!proto || !proto->connect) {
4589 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
4590 args[cur_arg]);
4591 goto error;
4592 }
4593
4594 if (port1 != port2) {
4595 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
4596 args[cur_arg], args[cur_arg+1]);
4597 goto error;
4598 }
4599
4600 cur_arg++;
4601 }
Christopher Faulet4dce5922020-03-30 13:54:42 +02004602 else if (strcmp(args[cur_arg], "port") == 0) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004603 const char *p, *end;
4604
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004605 if (!*(args[cur_arg+1])) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004606 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004607 goto error;
4608 }
4609 cur_arg++;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004610
4611 port = 0;
4612 release_sample_expr(port_expr);
4613 p = args[cur_arg]; end = p + strlen(p);
4614 port = read_uint(&p, end);
4615 if (p != end) {
4616 int idx = 0;
4617
4618 px->conf.args.ctx = ARGC_SRV;
4619 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4620 file, line, errmsg, &px->conf.args, NULL);
4621
4622 if (!port_expr) {
4623 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
4624 goto error;
4625 }
4626 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4627 memprintf(errmsg, "error detected while parsing port expression : "
4628 " fetch method '%s' extracts information from '%s', "
4629 "none of which is available here.\n",
4630 args[cur_arg], sample_src_names(port_expr->fetch->use));
4631 goto error;
4632 }
4633 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
4634 }
4635 else if (port > 65535 || port < 1) {
4636 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
4637 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004638 goto error;
4639 }
4640 }
4641 else if (strcmp(args[cur_arg], "comment") == 0) {
4642 if (!*(args[cur_arg+1])) {
4643 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4644 goto error;
4645 }
4646 cur_arg++;
4647 free(comment);
4648 comment = strdup(args[cur_arg]);
4649 if (!comment) {
4650 memprintf(errmsg, "out of memory");
4651 goto error;
4652 }
4653 }
4654 else if (strcmp(args[cur_arg], "send-proxy") == 0)
4655 conn_opts |= TCPCHK_OPT_SEND_PROXY;
Christopher Faulet085426a2020-03-30 13:07:02 +02004656 else if (strcmp(args[cur_arg], "via-socks4") == 0)
4657 conn_opts |= TCPCHK_OPT_SOCKS4;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004658 else if (strcmp(args[cur_arg], "linger") == 0)
4659 conn_opts |= TCPCHK_OPT_LINGER;
4660#ifdef USE_OPENSSL
4661 else if (strcmp(args[cur_arg], "ssl") == 0) {
4662 px->options |= PR_O_TCPCHK_SSL;
4663 conn_opts |= TCPCHK_OPT_SSL;
4664 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02004665 else if (strcmp(args[cur_arg], "sni") == 0) {
4666 if (!*(args[cur_arg+1])) {
4667 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4668 goto error;
4669 }
4670 cur_arg++;
4671 free(sni);
4672 sni = strdup(args[cur_arg]);
4673 if (!sni) {
4674 memprintf(errmsg, "out of memory");
4675 goto error;
4676 }
4677 }
Christopher Faulet98572322020-03-30 13:16:44 +02004678 else if (strcmp(args[cur_arg], "alpn") == 0) {
4679#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
4680 free(alpn);
4681 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
4682 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
4683 goto error;
4684 }
4685 cur_arg++;
4686#else
4687 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
4688 goto error;
4689#endif
4690 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004691#endif /* USE_OPENSSL */
4692
4693 else {
Christopher Faulet5c288742020-03-31 08:15:58 +02004694 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004695#ifdef USE_OPENSSL
Christopher Faulet98572322020-03-30 13:16:44 +02004696 ", 'ssl', 'sni', 'alpn'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004697#endif /* USE_OPENSSL */
Christopher Faulet4dce5922020-03-30 13:54:42 +02004698 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004699 args[cur_arg]);
4700 goto error;
4701 }
4702 cur_arg++;
4703 }
4704
4705 chk = calloc(1, sizeof(*chk));
4706 if (!chk) {
4707 memprintf(errmsg, "out of memory");
4708 goto error;
4709 }
Gaetan Rivet06d963a2020-02-21 18:49:05 +01004710 chk->action = TCPCHK_ACT_CONNECT;
4711 chk->comment = comment;
4712 chk->connect.port = port;
4713 chk->connect.options = conn_opts;
Christopher Faulet79b31d42020-03-30 13:00:05 +02004714 chk->connect.sni = sni;
Christopher Faulet98572322020-03-30 13:16:44 +02004715 chk->connect.alpn = alpn;
4716 chk->connect.alpn_len= alpn_len;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004717 chk->connect.port_expr= port_expr;
Christopher Faulet5c288742020-03-31 08:15:58 +02004718 if (sk)
4719 chk->connect.addr = *sk;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004720 return chk;
4721
4722 error:
Christopher Faulet98572322020-03-30 13:16:44 +02004723 free(alpn);
Christopher Faulet79b31d42020-03-30 13:00:05 +02004724 free(sni);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004725 free(comment);
Christopher Fauletb7d30092020-03-30 15:19:03 +02004726 release_sample_expr(port_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004727 return NULL;
4728}
4729
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004730static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004731 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004732{
4733 struct tcpcheck_rule *chk = NULL;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004734 char *comment = NULL, *data = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004735 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004736
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004737 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004738 if (!*(args[cur_arg+1])) {
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004739 memprintf(errmsg, "'%s' expects a %s as argument",
4740 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004741 goto error;
4742 }
4743
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004744 data = args[cur_arg+1];
4745
4746 cur_arg += 2;
4747 while (*(args[cur_arg])) {
4748 if (strcmp(args[cur_arg], "comment") == 0) {
4749 if (!*(args[cur_arg+1])) {
4750 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4751 goto error;
4752 }
4753 cur_arg++;
4754 free(comment);
4755 comment = strdup(args[cur_arg]);
4756 if (!comment) {
4757 memprintf(errmsg, "out of memory");
4758 goto error;
4759 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004760 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004761 else if (strcmp(args[cur_arg], "log-format") == 0) {
4762 if (type == TCPCHK_SEND_BINARY)
4763 type = TCPCHK_SEND_BINARY_LF;
4764 else if (type == TCPCHK_SEND_STRING)
4765 type = TCPCHK_SEND_STRING_LF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004766 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004767 else {
4768 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
4769 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004770 goto error;
4771 }
4772 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004773 }
4774
4775 chk = calloc(1, sizeof(*chk));
4776 if (!chk) {
4777 memprintf(errmsg, "out of memory");
4778 goto error;
4779 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004780 chk->action = TCPCHK_ACT_SEND;
4781 chk->comment = comment;
4782 chk->send.type = type;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004783
4784 switch (chk->send.type) {
4785 case TCPCHK_SEND_STRING:
4786 chk->send.data = ist2(strdup(data), strlen(data));
4787 if (!isttest(chk->send.data)) {
4788 memprintf(errmsg, "out of memory");
4789 goto error;
4790 }
4791 break;
4792 case TCPCHK_SEND_BINARY:
4793 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
4794 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
4795 goto error;
4796 }
4797 break;
4798 case TCPCHK_SEND_STRING_LF:
4799 case TCPCHK_SEND_BINARY_LF:
4800 LIST_INIT(&chk->send.fmt);
4801 px->conf.args.ctx = ARGC_SRV;
4802 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4803 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
4804 goto error;
4805 }
4806 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02004807 case TCPCHK_SEND_HTTP:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004808 case TCPCHK_SEND_UNDEF:
4809 goto error;
4810 }
4811
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004812 return chk;
4813
4814 error:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004815 free(chk);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004816 free(comment);
4817 return NULL;
4818}
4819
Christopher Faulete5870d82020-04-15 11:32:03 +02004820static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
4821 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004822{
4823 struct tcpcheck_rule *chk = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004824 struct tcpcheck_http_hdr *hdr = NULL;
4825 struct http_hdr hdrs[global.tune.max_http_hdr];
4826 char *meth = NULL, *uri = NULL, *vsn = NULL;
4827 char *body = NULL, *comment = NULL;
4828 unsigned int flags = 0;
4829 int i = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004830
4831 cur_arg++;
4832 while (*(args[cur_arg])) {
Christopher Faulete5870d82020-04-15 11:32:03 +02004833 if (strcmp(args[cur_arg], "meth") == 0) {
4834 if (!*(args[cur_arg+1])) {
4835 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4836 goto error;
4837 }
4838 cur_arg++;
4839 meth = args[cur_arg];
4840 }
4841 else if (strcmp(args[cur_arg], "uri") == 0) {
4842 if (!*(args[cur_arg+1])) {
4843 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4844 goto error;
4845 }
4846 cur_arg++;
4847 uri = args[cur_arg];
4848 // TODO: log-format uri
4849 }
4850 else if (strcmp(args[cur_arg], "vsn") == 0) {
4851 if (!*(args[cur_arg+1])) {
4852 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4853 goto error;
4854 }
4855 cur_arg++;
4856 vsn = args[cur_arg];
4857 }
4858 else if (strcmp(args[cur_arg], "hdr") == 0) {
4859 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
4860 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
4861 goto error;
4862 }
4863 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
4864 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
4865 i++;
4866 cur_arg += 2;
4867 }
4868 else if (strcmp(args[cur_arg], "body") == 0) {
4869 if (!*(args[cur_arg+1])) {
4870 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4871 goto error;
4872 }
4873 cur_arg++;
4874 body = args[cur_arg];
4875 // TODO: log-format body
4876 }
4877 else if (strcmp(args[cur_arg], "comment") == 0) {
4878 if (!*(args[cur_arg+1])) {
4879 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4880 goto error;
4881 }
4882 cur_arg++;
4883 free(comment);
4884 comment = strdup(args[cur_arg]);
4885 if (!comment) {
4886 memprintf(errmsg, "out of memory");
4887 goto error;
4888 }
4889 }
4890 else {
4891 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'hdr' and 'body' but got '%s' as argument.",
4892 args[cur_arg]);
4893 goto error;
4894 }
4895 cur_arg++;
4896 }
4897
4898 hdrs[i].n = hdrs[i].v = IST_NULL;
4899
4900 chk = calloc(1, sizeof(*chk));
4901 if (!chk) {
4902 memprintf(errmsg, "out of memory");
4903 goto error;
4904 }
4905 chk->action = TCPCHK_ACT_SEND;
4906 chk->comment = comment; comment = NULL;
4907 chk->send.type = TCPCHK_SEND_HTTP;
4908 chk->send.http.flags = flags;
4909 LIST_INIT(&chk->send.http.hdrs);
4910
4911 if (meth) {
4912 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4913 chk->send.http.meth.str.area = strdup(meth);
4914 chk->send.http.meth.str.data = strlen(meth);
4915 if (!chk->send.http.meth.str.area) {
4916 memprintf(errmsg, "out of memory");
4917 goto error;
4918 }
4919 }
4920 if (uri) {
4921 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
4922 if (!isttest(chk->send.http.uri)) {
4923 memprintf(errmsg, "out of memory");
4924 goto error;
4925 }
4926 }
4927 if (vsn) {
4928 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
4929 if (!isttest(chk->send.http.vsn)) {
4930 memprintf(errmsg, "out of memory");
4931 goto error;
4932 }
4933 }
4934 for (i = 0; hdrs[i].n.len; i++) {
4935 hdr = calloc(1, sizeof(*hdr));
4936 if (!hdr) {
4937 memprintf(errmsg, "out of memory");
4938 goto error;
4939 }
4940 LIST_INIT(&hdr->value);
4941 hdr->name = ist2(strdup(hdrs[i].n.ptr), hdrs[i].n.len);
4942 if (!hdr->name.ptr) {
4943 memprintf(errmsg, "out of memory");
4944 goto error;
4945 }
4946
4947 hdrs[i].v.ptr[hdrs[i].v.len] = '\0';
4948 if (!parse_logformat_string(hdrs[i].v.ptr, px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
4949 goto error;
4950 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
4951 hdr = NULL;
4952 }
4953
4954 if (body) {
4955 chk->send.http.body = ist2(strdup(body), strlen(body));
4956 if (!isttest(chk->send.http.body)) {
4957 memprintf(errmsg, "out of memory");
4958 goto error;
4959 }
4960 }
4961
4962 return chk;
4963
4964 error:
4965 free_tcpcheck_http_hdr(hdr);
4966 free_tcpcheck(chk, 0);
4967 free(comment);
4968 return NULL;
4969}
4970
4971static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
4972{
4973 struct logformat_node *lf, *lfb;
4974 struct tcpcheck_http_hdr *hdr, *bhdr;
4975
4976
4977 if (new->send.http.meth.str.area) {
4978 free(old->send.http.meth.str.area);
4979 old->send.http.meth.meth = new->send.http.meth.meth;
4980 old->send.http.meth.str.area = new->send.http.meth.str.area;
4981 old->send.http.meth.str.data = new->send.http.meth.str.data;
4982 new->send.http.meth.str = BUF_NULL;
4983 }
4984
4985 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4986 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
4987 free(old->send.http.uri.ptr);
4988 else
4989 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4990 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4991 old->send.http.uri = new->send.http.uri;
4992 new->send.http.uri = IST_NULL;
4993 }
4994 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4995 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
4996 free(old->send.http.uri.ptr);
4997 else
4998 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4999 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
5000 LIST_INIT(&old->send.http.uri_fmt);
5001 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
5002 LIST_DEL(&lf->list);
5003 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
5004 }
5005 }
5006
5007 if (isttest(new->send.http.vsn)) {
5008 free(old->send.http.vsn.ptr);
5009 old->send.http.vsn = new->send.http.vsn;
5010 new->send.http.vsn = IST_NULL;
5011 }
5012
5013 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
5014 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
5015 LIST_DEL(&hdr->list);
5016 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
5017 }
5018
5019 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
5020 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
5021 free(old->send.http.body.ptr);
5022 else
5023 free_tcpcheck_fmt(&old->send.http.body_fmt);
5024 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
5025 old->send.http.body = new->send.http.body;
5026 new->send.http.body = IST_NULL;
5027 }
5028 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
5029 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
5030 free(old->send.http.body.ptr);
5031 else
5032 free_tcpcheck_fmt(&old->send.http.body_fmt);
5033 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
5034 LIST_INIT(&old->send.http.body_fmt);
5035 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
5036 LIST_DEL(&lf->list);
5037 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
5038 }
5039 }
5040}
5041
5042static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
5043{
5044 struct tcpcheck_rule *r;
5045
5046 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
5047 r = get_first_tcpcheck_rule(rules);
5048 if (r && r->action == TCPCHK_ACT_CONNECT)
5049 r = get_next_tcpcheck_rule(rules, r);
5050 if (!r || r->action != TCPCHK_ACT_SEND)
5051 LIST_ADD(rules->list, &chk->list);
5052 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
5053 LIST_DEL(&r->list);
5054 free_tcpcheck(r, 0);
5055 LIST_ADD(rules->list, &chk->list);
5056 }
5057 else {
5058 tcpcheck_overwrite_send_http_rule(r, chk);
5059 free_tcpcheck(chk, 0);
5060 }
5061 }
5062 else {
5063 r = get_last_tcpcheck_rule(rules);
5064 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
5065 /* no error */;
5066 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
5067 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
5068 chk->index+1);
5069 return 0;
5070 }
5071 else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
5072 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
5073 chk->index+1);
5074 return 0;
5075 }
5076 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
5077 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
5078 chk->index+1);
5079 return 0;
5080 }
5081
5082 if (chk->action == TCPCHK_ACT_SEND) {
5083 r = get_first_tcpcheck_rule(rules);
5084 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
5085 tcpcheck_overwrite_send_http_rule(r, chk);
5086 free_tcpcheck(chk, 0);
5087 LIST_DEL(&r->list);
5088 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
5089 chk = r;
5090 }
5091 }
5092 LIST_ADDQ(rules->list, &chk->list);
5093 }
5094 return 1;
5095}
5096
5097static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
5098 const char *file, int line, char **errmsg)
5099{
5100 struct tcpcheck_rule *chk = NULL;
5101 char *comment = NULL;
5102
5103 if (!*(args[cur_arg+1])) {
5104 memprintf(errmsg, "expects a string as argument");
5105 goto error;
5106 }
5107 cur_arg++;
5108 comment = strdup(args[cur_arg]);
5109 if (!comment) {
5110 memprintf(errmsg, "out of memory");
5111 goto error;
5112 }
5113
5114 chk = calloc(1, sizeof(*chk));
5115 if (!chk) {
5116 memprintf(errmsg, "out of memory");
5117 goto error;
5118 }
5119 chk->action = TCPCHK_ACT_COMMENT;
5120 chk->comment = comment;
5121 return chk;
5122
5123 error:
5124 free(comment);
5125 return NULL;
5126}
5127
5128static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
5129 struct list *rules, unsigned int proto,
5130 const char *file, int line, char **errmsg)
5131{
5132 struct tcpcheck_rule *prev_check, *chk = NULL;
5133 struct sample_expr *status_expr = NULL;
5134 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
5135 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
5136 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
5137 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
5138 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
5139 long min_recv = -1;
5140 int inverse = 0, with_capture = 0;
5141
5142 str = on_success_msg = on_error_msg = comment = pattern = NULL;
5143 if (!*(args[cur_arg+1])) {
5144 memprintf(errmsg, "expects at least a matching pattern as arguments");
5145 goto error;
5146 }
5147
5148 cur_arg++;
5149 while (*(args[cur_arg])) {
5150 int in_pattern = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005151
5152 rescan:
5153 if (strcmp(args[cur_arg], "min-recv") == 0) {
5154 if (in_pattern) {
5155 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5156 goto error;
5157 }
5158 if (!*(args[cur_arg+1])) {
5159 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
5160 goto error;
5161 }
5162 /* Use an signed integer here because of chksize */
5163 cur_arg++;
5164 min_recv = atol(args[cur_arg]);
5165 if (min_recv < -1 || min_recv > INT_MAX) {
5166 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
5167 goto error;
5168 }
5169 }
5170 else if (*(args[cur_arg]) == '!') {
5171 in_pattern = 1;
5172 while (*(args[cur_arg]) == '!') {
5173 inverse = !inverse;
5174 args[cur_arg]++;
5175 }
5176 if (!*(args[cur_arg]))
5177 cur_arg++;
5178 goto rescan;
5179 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005180 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005181 if (type != TCPCHK_EXPECT_UNDEF) {
5182 memprintf(errmsg, "only on pattern expected");
5183 goto error;
5184 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005185 if (proto != TCPCHK_RULES_HTTP_CHK)
5186 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
5187 else
5188 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005189
5190 if (!*(args[cur_arg+1])) {
5191 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
5192 goto error;
5193 }
5194 cur_arg++;
5195 pattern = args[cur_arg];
5196 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005197 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
5198 if (proto == TCPCHK_RULES_HTTP_CHK)
5199 goto bad_http_kw;
5200 if (type != TCPCHK_EXPECT_UNDEF) {
5201 memprintf(errmsg, "only on pattern expected");
5202 goto error;
5203 }
5204 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
5205
5206 if (!*(args[cur_arg+1])) {
5207 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
5208 goto error;
5209 }
5210 cur_arg++;
5211 pattern = args[cur_arg];
5212 }
5213 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
5214 if (proto != TCPCHK_RULES_HTTP_CHK)
5215 goto bad_tcp_kw;
5216 if (type != TCPCHK_EXPECT_UNDEF) {
5217 memprintf(errmsg, "only on pattern expected");
5218 goto error;
5219 }
5220 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
5221
5222 if (!*(args[cur_arg+1])) {
5223 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
5224 goto error;
5225 }
5226 cur_arg++;
5227 pattern = args[cur_arg];
5228 }
Christopher Faulet9e6ed152020-04-03 15:24:06 +02005229 else if (strcmp(args[cur_arg], "custom") == 0) {
5230 if (in_pattern) {
5231 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5232 goto error;
5233 }
5234 if (type != TCPCHK_EXPECT_UNDEF) {
5235 memprintf(errmsg, "only on pattern expected");
5236 goto error;
5237 }
5238 type = TCPCHK_EXPECT_CUSTOM;
5239 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005240 else if (strcmp(args[cur_arg], "comment") == 0) {
5241 if (in_pattern) {
5242 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5243 goto error;
5244 }
5245 if (!*(args[cur_arg+1])) {
5246 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5247 goto error;
5248 }
5249 cur_arg++;
5250 free(comment);
5251 comment = strdup(args[cur_arg]);
5252 if (!comment) {
5253 memprintf(errmsg, "out of memory");
5254 goto error;
5255 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005256 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005257 else if (strcmp(args[cur_arg], "on-success") == 0) {
5258 if (in_pattern) {
5259 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5260 goto error;
5261 }
5262 if (!*(args[cur_arg+1])) {
5263 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5264 goto error;
5265 }
5266 cur_arg++;
5267 free(on_success_msg);
5268 on_success_msg = strdup(args[cur_arg]);
5269 if (!on_success_msg) {
5270 memprintf(errmsg, "out of memory");
5271 goto error;
5272 }
5273 }
5274 else if (strcmp(args[cur_arg], "on-error") == 0) {
5275 if (in_pattern) {
5276 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5277 goto error;
5278 }
5279 if (!*(args[cur_arg+1])) {
5280 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5281 goto error;
5282 }
5283 cur_arg++;
5284 free(on_error_msg);
5285 on_error_msg = strdup(args[cur_arg]);
5286 if (!on_error_msg) {
5287 memprintf(errmsg, "out of memory");
5288 goto error;
5289 }
Christopher Fauletec07e382020-04-07 14:56:26 +02005290 }
5291 else if (strcmp(args[cur_arg], "ok-status") == 0) {
5292 if (in_pattern) {
5293 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5294 goto error;
5295 }
5296 if (!*(args[cur_arg+1])) {
5297 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5298 goto error;
5299 }
5300 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
5301 ok_st = HCHK_STATUS_L7OKD;
5302 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
5303 ok_st = HCHK_STATUS_L7OKCD;
5304 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
5305 ok_st = HCHK_STATUS_L6OK;
5306 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
5307 ok_st = HCHK_STATUS_L4OK;
5308 else {
5309 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
5310 args[cur_arg], args[cur_arg+1]);
5311 goto error;
5312 }
5313 cur_arg++;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005314 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005315 else if (strcmp(args[cur_arg], "error-status") == 0) {
5316 if (in_pattern) {
5317 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5318 goto error;
5319 }
5320 if (!*(args[cur_arg+1])) {
5321 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5322 goto error;
5323 }
5324 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
5325 err_st = HCHK_STATUS_L7RSP;
5326 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
5327 err_st = HCHK_STATUS_L7STS;
5328 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
5329 err_st = HCHK_STATUS_L6RSP;
5330 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
5331 err_st = HCHK_STATUS_L4CON;
5332 else {
5333 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
5334 args[cur_arg], args[cur_arg+1]);
5335 goto error;
5336 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005337 cur_arg++;
5338 }
5339 else if (strcmp(args[cur_arg], "status-code") == 0) {
5340 int idx = 0;
5341
5342 if (in_pattern) {
5343 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5344 goto error;
5345 }
5346 if (!*(args[cur_arg+1])) {
5347 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
5348 goto error;
5349 }
5350
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005351 cur_arg++;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005352 release_sample_expr(status_expr);
5353 px->conf.args.ctx = ARGC_SRV;
5354 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
5355 file, line, errmsg, &px->conf.args, NULL);
5356 if (!status_expr) {
5357 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
5358 goto error;
5359 }
5360 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
5361 memprintf(errmsg, "error detected while parsing status-code expression : "
5362 " fetch method '%s' extracts information from '%s', "
5363 "none of which is available here.\n",
5364 args[cur_arg], sample_src_names(status_expr->fetch->use));
5365 goto error;
5366 }
5367 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005368 }
5369 else if (strcmp(args[cur_arg], "tout-status") == 0) {
5370 if (in_pattern) {
5371 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5372 goto error;
5373 }
5374 if (!*(args[cur_arg+1])) {
5375 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5376 goto error;
5377 }
5378 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
5379 tout_st = HCHK_STATUS_L7TOUT;
5380 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
5381 tout_st = HCHK_STATUS_L6TOUT;
5382 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
5383 tout_st = HCHK_STATUS_L4TOUT;
5384 else {
5385 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
5386 args[cur_arg], args[cur_arg+1]);
5387 goto error;
5388 }
5389 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005390 }
5391 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005392 if (proto == TCPCHK_RULES_HTTP_CHK) {
5393 bad_http_kw:
5394 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
5395 " or comment but got '%s' as argument.", args[cur_arg]);
5396 }
5397 else {
5398 bad_tcp_kw:
5399 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
5400 " or comment but got '%s' as argument.", args[cur_arg]);
5401 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005402 goto error;
5403 }
5404
5405 cur_arg++;
5406 }
5407
5408 if (comment) {
5409 char *p = comment;
5410
5411 while (*p) {
5412 if (*p == '\\') {
5413 p++;
5414 if (!*p || !isdigit((unsigned char)*p) ||
5415 (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
5416 memprintf(errmsg, "invalid backreference in 'comment' argument");
5417 goto error;
5418 }
5419 with_capture = 1;
5420 }
5421 p++;
5422 }
5423 if (with_capture && !inverse)
5424 memprintf(errmsg, "using backreference in a positive expect comment is useless");
5425 }
5426
5427 chk = calloc(1, sizeof(*chk));
5428 if (!chk) {
5429 memprintf(errmsg, "out of memory");
5430 goto error;
5431 }
5432 chk->action = TCPCHK_ACT_EXPECT;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005433 LIST_INIT(&chk->expect.onerror_fmt);
5434 LIST_INIT(&chk->expect.onsuccess_fmt);
5435 chk->comment = comment; comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005436 chk->expect.type = type;
5437 chk->expect.min_recv = min_recv;
Christopher Faulet12d57402020-04-10 09:58:42 +02005438 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
5439 chk->expect.flags |= (with_capture ? TCPCHK_EXPT_FL_CAP : 0);
Christopher Fauletec07e382020-04-07 14:56:26 +02005440 chk->expect.ok_status = ok_st;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005441 chk->expect.err_status = err_st;
5442 chk->expect.tout_status = tout_st;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005443 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005444
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005445 if (on_success_msg) {
5446 px->conf.args.ctx = ARGC_SRV;
5447 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
5448 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
5449 goto error;
5450 }
5451 free(on_success_msg);
5452 on_success_msg = NULL;
5453 }
5454 if (on_error_msg) {
5455 px->conf.args.ctx = ARGC_SRV;
5456 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
5457 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
5458 goto error;
5459 }
5460 free(on_error_msg);
5461 on_error_msg = NULL;
5462 }
5463
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005464 switch (chk->expect.type) {
5465 case TCPCHK_EXPECT_STRING:
Christopher Faulete5870d82020-04-15 11:32:03 +02005466 case TCPCHK_EXPECT_HTTP_STATUS:
5467 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02005468 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
5469 if (!chk->expect.data.ptr) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005470 memprintf(errmsg, "out of memory");
5471 goto error;
5472 }
5473 break;
5474 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02005475 if (parse_binary(pattern, &chk->expect.data.ptr, (int *)&chk->expect.data.len, errmsg) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005476 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
5477 goto error;
5478 }
5479 case TCPCHK_EXPECT_REGEX:
5480 case TCPCHK_EXPECT_REGEX_BINARY:
Christopher Faulete5870d82020-04-15 11:32:03 +02005481 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
5482 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005483 chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
5484 if (!chk->expect.regex)
5485 goto error;
5486 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02005487 case TCPCHK_EXPECT_CUSTOM:
5488 chk->expect.custom = NULL; /* Must be defined by the caller ! */
5489 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005490 case TCPCHK_EXPECT_UNDEF:
5491 free(chk);
5492 memprintf(errmsg, "pattern not found");
5493 goto error;
5494 }
5495
5496 /* All tcp-check expect points back to the first inverse expect rule in
5497 * a chain of one or more expect rule, potentially itself.
5498 */
5499 chk->expect.head = chk;
5500 list_for_each_entry_rev(prev_check, rules, list) {
5501 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet12d57402020-04-10 09:58:42 +02005502 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005503 chk->expect.head = prev_check;
5504 continue;
5505 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01005506 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005507 break;
5508 }
5509 return chk;
5510
5511 error:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005512 free_tcpcheck(chk, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005513 free(str);
5514 free(comment);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005515 free(on_success_msg);
5516 free(on_error_msg);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005517 release_sample_expr(status_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005518 return NULL;
5519}
5520
5521/* Parses the "tcp-check" proxy keyword */
5522static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5523 struct proxy *defpx, const char *file, int line,
5524 char **errmsg)
5525{
Christopher Faulet404f9192020-04-09 23:13:54 +02005526 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005527 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005528 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005529
5530 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5531 ret = 1;
5532
Christopher Faulet404f9192020-04-09 23:13:54 +02005533 /* Deduce the ruleset name from the proxy info */
5534 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5535 ((curpx == defpx) ? "defaults" : curpx->id),
5536 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005537
Christopher Faulet404f9192020-04-09 23:13:54 +02005538 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5539 if (rs == NULL) {
5540 rs = tcpcheck_ruleset_create(b_orig(&trash));
5541 if (rs == NULL) {
5542 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005543 goto error;
5544 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005545 }
5546
Gaetan Rivet5301b012020-02-25 17:19:17 +01005547 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005548 if (!LIST_ISEMPTY(&rs->rules)) {
5549 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005550 index = chk->index + 1;
5551 }
5552
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005553 cur_arg = 1;
5554 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005555 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005556 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005557 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005558 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005559 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005560 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005561 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005562 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005563 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5564
5565 if (!kw) {
5566 action_kw_tcp_check_build_list(&trash);
5567 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5568 "%s%s. but got '%s'",
5569 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5570 goto error;
5571 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005572 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005573 }
5574
5575 if (!chk) {
5576 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5577 goto error;
5578 }
5579 ret = (*errmsg != NULL); /* Handle warning */
5580
5581 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005582 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005583 LIST_ADDQ(&rs->rules, &chk->list);
5584
5585 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5586 !(curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK)) {
5587 /* Use this ruleset if the proxy already has tcp-check enabled */
5588 curpx->tcpcheck_rules.list = &rs->rules;
5589 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5590 }
5591 else {
5592 /* mark this ruleset as unused for now */
5593 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5594 }
5595
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005596 return ret;
5597
5598 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005599 free_tcpcheck(chk, 0);
5600 tcpcheck_ruleset_release(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005601 return -1;
5602}
5603
Christopher Faulet51b129f2020-04-09 15:54:18 +02005604/* Parses the "http-check" proxy keyword */
5605static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5606 struct proxy *defpx, const char *file, int line,
5607 char **errmsg)
5608{
Christopher Faulete5870d82020-04-15 11:32:03 +02005609 struct tcpcheck_ruleset *rs = NULL;
5610 struct tcpcheck_rule *chk = NULL;
5611 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005612
5613 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5614 ret = 1;
5615
5616 cur_arg = 1;
5617 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5618 /* enable a graceful server shutdown on an HTTP 404 response */
5619 curpx->options |= PR_O_DISABLE404;
5620 if (too_many_args(1, args, errmsg, NULL))
5621 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005622 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005623 }
5624 else if (strcmp(args[cur_arg], "send-state") == 0) {
5625 /* enable emission of the apparent state of a server in HTTP checks */
5626 curpx->options2 |= PR_O2_CHK_SNDST;
5627 if (too_many_args(1, args, errmsg, NULL))
5628 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005629 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005630 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005631
Christopher Faulete5870d82020-04-15 11:32:03 +02005632 /* Deduce the ruleset name from the proxy info */
5633 chunk_printf(&trash, "*http-check-%s_%s-%d",
5634 ((curpx == defpx) ? "defaults" : curpx->id),
5635 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005636
Christopher Faulete5870d82020-04-15 11:32:03 +02005637 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5638 if (rs == NULL) {
5639 rs = tcpcheck_ruleset_create(b_orig(&trash));
5640 if (rs == NULL) {
5641 memprintf(errmsg, "out of memory.\n");
5642 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005643 }
5644 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005645
Christopher Faulete5870d82020-04-15 11:32:03 +02005646 index = 0;
5647 if (!LIST_ISEMPTY(&rs->rules)) {
5648 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5649 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5650 index = chk->index + 1;
5651 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005652
Christopher Faulete5870d82020-04-15 11:32:03 +02005653 if (strcmp(args[cur_arg], "connect") == 0)
5654 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5655 else if (strcmp(args[cur_arg], "send") == 0)
5656 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5657 else if (strcmp(args[cur_arg], "expect") == 0)
5658 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5659 file, line, errmsg);
5660 else if (strcmp(args[cur_arg], "comment") == 0)
5661 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5662 else {
5663 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005664
Christopher Faulete5870d82020-04-15 11:32:03 +02005665 if (!kw) {
5666 action_kw_tcp_check_build_list(&trash);
5667 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5668 " 'send', 'expect'%s%s. but got '%s'",
5669 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5670 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005671 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005672 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5673 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005674
Christopher Faulete5870d82020-04-15 11:32:03 +02005675 if (!chk) {
5676 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5677 goto error;
5678 }
5679 ret = (*errmsg != NULL); /* Handle warning */
5680
5681 chk->index = index;
5682 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5683 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5684 /* Use this ruleset if the proxy already has http-check enabled */
5685 curpx->tcpcheck_rules.list = &rs->rules;
5686 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5687 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5688 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5689 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005690 goto error;
5691 }
5692 }
5693 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005694 /* mark this ruleset as unused for now */
5695 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5696 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005697 }
5698
Christopher Faulete5870d82020-04-15 11:32:03 +02005699 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005700 return ret;
5701
5702 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005703 free_tcpcheck(chk, 0);
5704 tcpcheck_ruleset_release(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005705 return -1;
5706}
5707
Christopher Faulete9111b62020-04-09 18:12:08 +02005708/* Parses the "external-check" proxy keyword */
5709static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5710 struct proxy *defpx, const char *file, int line,
5711 char **errmsg)
5712{
5713 int cur_arg, ret = 0;
5714
5715 cur_arg = 1;
5716 if (!*(args[cur_arg])) {
5717 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5718 goto error;
5719 }
5720
5721 if (strcmp(args[cur_arg], "command") == 0) {
5722 if (too_many_args(2, args, errmsg, NULL))
5723 goto error;
5724 if (!*(args[cur_arg+1])) {
5725 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5726 goto error;
5727 }
5728 free(curpx->check_command);
5729 curpx->check_command = strdup(args[cur_arg+1]);
5730 }
5731 else if (strcmp(args[cur_arg], "path") == 0) {
5732 if (too_many_args(2, args, errmsg, NULL))
5733 goto error;
5734 if (!*(args[cur_arg+1])) {
5735 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5736 goto error;
5737 }
5738 free(curpx->check_path);
5739 curpx->check_path = strdup(args[cur_arg+1]);
5740 }
5741 else {
5742 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5743 args[0], args[1]);
5744 goto error;
5745 }
5746
5747 ret = (*errmsg != NULL); /* Handle warning */
5748 return ret;
5749
5750error:
5751 return -1;
5752}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005753
Christopher Faulet430e4802020-04-09 15:28:16 +02005754/* Parses the "option tcp-check" proxy keyword */
5755int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5756 const char *file, int line)
5757{
Christopher Faulet404f9192020-04-09 23:13:54 +02005758 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005759 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5760 int err_code = 0;
5761
5762 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5763 err_code |= ERR_WARN;
5764
5765 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5766 goto out;
5767
Christopher Faulet404f9192020-04-09 23:13:54 +02005768 curpx->options2 &= ~PR_O2_CHK_ANY;
5769 curpx->options2 |= PR_O2_TCPCHK_CHK;
5770
5771 if (!(rules->flags & TCPCHK_RULES_PROTO_CHK)) {
5772 /* If a tcp-check rulesset is already set, do nothing */
5773 if (rules->list)
5774 goto out;
5775
5776 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5777 * get it.
5778 */
5779 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5780 goto curpx_ruleset;
5781
5782 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5783 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5784 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5785 if (rs)
5786 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005787 }
5788
Christopher Faulet404f9192020-04-09 23:13:54 +02005789 curpx_ruleset:
5790 /* Deduce the ruleset name from the proxy info */
5791 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5792 ((curpx == defpx) ? "defaults" : curpx->id),
5793 curpx->conf.file, curpx->conf.line);
5794
5795 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5796 if (rs == NULL) {
5797 rs = tcpcheck_ruleset_create(b_orig(&trash));
5798 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005799 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5800 goto error;
5801 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005802 }
5803
Christopher Faulet404f9192020-04-09 23:13:54 +02005804 ruleset_found:
5805 free_tcpcheck_vars(&rules->preset_vars);
5806 rules->list = NULL;
5807 rules->flags = 0;
5808
Christopher Faulet404f9192020-04-09 23:13:54 +02005809 rules->list = &rs->rules;
Christopher Faulet430e4802020-04-09 15:28:16 +02005810
5811 out:
5812 return err_code;
5813
5814 error:
5815 err_code |= ERR_ALERT | ERR_FATAL;
5816 goto out;
5817}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005818
5819/* Parses the "option redis-check" proxy keyword */
5820int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5821 const char *file, int line)
5822{
5823 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5824 static char *redis_res = "+PONG\r\n";
5825
5826 struct tcpcheck_ruleset *rs = NULL;
5827 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5828 struct tcpcheck_rule *chk;
5829 char *errmsg = NULL;
5830 int err_code = 0;
5831
5832 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5833 err_code |= ERR_WARN;
5834
5835 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5836 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005837
5838 curpx->options2 &= ~PR_O2_CHK_ANY;
5839 curpx->options2 |= PR_O2_TCPCHK_CHK;
5840
5841 free_tcpcheck_vars(&rules->preset_vars);
5842 rules->list = NULL;
5843 rules->flags = 0;
5844
5845 rs = tcpcheck_ruleset_lookup("*redis-check");
5846 if (rs)
5847 goto ruleset_found;
5848
5849 rs = tcpcheck_ruleset_create("*redis-check");
5850 if (rs == NULL) {
5851 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5852 goto error;
5853 }
5854
5855 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5856 1, curpx, &rs->rules, file, line, &errmsg);
5857 if (!chk) {
5858 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5859 goto error;
5860 }
5861 chk->index = 0;
5862 LIST_ADDQ(&rs->rules, &chk->list);
5863
5864 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5865 "error-status", "L7STS",
5866 "on-error", "%[check.payload(),cut_crlf]",
5867 "on-success", "Redis server is ok",
5868 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005869 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005870 if (!chk) {
5871 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5872 goto error;
5873 }
5874 chk->index = 1;
5875 LIST_ADDQ(&rs->rules, &chk->list);
5876
5877 LIST_ADDQ(&tcpchecks_list, &rs->list);
5878
5879 ruleset_found:
5880 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005881 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005882
5883 out:
5884 free(errmsg);
5885 return err_code;
5886
5887 error:
5888 tcpcheck_ruleset_release(rs);
5889 err_code |= ERR_ALERT | ERR_FATAL;
5890 goto out;
5891}
5892
Christopher Faulet811f78c2020-04-01 11:10:27 +02005893
5894/* Parses the "option ssl-hello-chk" proxy keyword */
5895int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5896 const char *file, int line)
5897{
5898 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5899 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5900 *
5901 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5902 */
5903 static char sslv3_client_hello[] = {
5904 "16" /* ContentType : 0x16 = Hanshake */
5905 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5906 "0079" /* ContentLength : 0x79 bytes after this one */
5907 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5908 "000075" /* HandshakeLength : 0x75 bytes after this one */
5909 "0300" /* Hello Version : 0x0300 = v3 */
5910 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5911 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5912 "00" /* Session ID length : empty (no session ID) */
5913 "004E" /* Cipher Suite Length : 78 bytes after this one */
5914 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5915 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5916 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5917 "000D" "000E" "000F" "0010" /* various bit lengths, */
5918 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5919 "0015" "0016" "0017" "0018"
5920 "0019" "001A" "001B" "002F"
5921 "0030" "0031" "0032" "0033"
5922 "0034" "0035" "0036" "0037"
5923 "0038" "0039" "003A"
5924 "01" /* Compression Length : 0x01 = 1 byte for types */
5925 "00" /* Compression Type : 0x00 = NULL compression */
5926 };
5927
5928 struct tcpcheck_ruleset *rs = NULL;
5929 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5930 struct tcpcheck_rule *chk;
5931 char *errmsg = NULL;
5932 int err_code = 0;
5933
5934 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5935 err_code |= ERR_WARN;
5936
5937 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5938 goto out;
5939
Christopher Faulet811f78c2020-04-01 11:10:27 +02005940 curpx->options2 &= ~PR_O2_CHK_ANY;
5941 curpx->options2 |= PR_O2_TCPCHK_CHK;
5942
5943 free_tcpcheck_vars(&rules->preset_vars);
5944 rules->list = NULL;
5945 rules->flags = 0;
5946
5947 rs = tcpcheck_ruleset_lookup("*ssl-hello-check");
5948 if (rs)
5949 goto ruleset_found;
5950
5951 rs = tcpcheck_ruleset_create("*ssl-hello-check");
5952 if (rs == NULL) {
5953 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5954 goto error;
5955 }
5956
5957 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5958 1, curpx, &rs->rules, file, line, &errmsg);
5959 if (!chk) {
5960 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5961 goto error;
5962 }
5963 chk->index = 0;
5964 LIST_ADDQ(&rs->rules, &chk->list);
5965
5966 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005967 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005968 "error-status", "L6RSP", "tout-status", "L6TOUT",
5969 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005970 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005971 if (!chk) {
5972 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5973 goto error;
5974 }
5975 chk->index = 1;
5976 LIST_ADDQ(&rs->rules, &chk->list);
5977
5978 LIST_ADDQ(&tcpchecks_list, &rs->list);
5979
5980 ruleset_found:
5981 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005982 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005983
5984 out:
5985 free(errmsg);
5986 return err_code;
5987
5988 error:
5989 tcpcheck_ruleset_release(rs);
5990 err_code |= ERR_ALERT | ERR_FATAL;
5991 goto out;
5992}
5993
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005994/* Parses the "option smtpchk" proxy keyword */
5995int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5996 const char *file, int line)
5997{
5998 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5999
6000 struct tcpcheck_ruleset *rs = NULL;
6001 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6002 struct tcpcheck_rule *chk;
6003 struct tcpcheck_var *var = NULL;
6004 char *cmd = NULL, *errmsg = NULL;
6005 int err_code = 0;
6006
6007 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6008 err_code |= ERR_WARN;
6009
6010 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6011 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006012
6013 curpx->options2 &= ~PR_O2_CHK_ANY;
6014 curpx->options2 |= PR_O2_TCPCHK_CHK;
6015
6016 free_tcpcheck_vars(&rules->preset_vars);
6017 rules->list = NULL;
6018 rules->flags = 0;
6019
6020 cur_arg += 2;
6021 if (*args[cur_arg] && *args[cur_arg+1] &&
6022 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
6023 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
6024 if (cmd)
6025 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
6026 }
6027 else {
6028 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
6029 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
6030 cmd = strdup("HELO localhost");
6031 }
6032
6033 var = tcpcheck_var_create("check.smtp_cmd");
6034 if (cmd == NULL || var == NULL) {
6035 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6036 goto error;
6037 }
6038 var->data.type = SMP_T_STR;
6039 var->data.u.str.area = cmd;
6040 var->data.u.str.data = strlen(cmd);
6041 LIST_INIT(&var->list);
6042 LIST_ADDQ(&rules->preset_vars, &var->list);
6043 cmd = NULL;
6044 var = NULL;
6045
6046 rs = tcpcheck_ruleset_lookup("*smtp-check");
6047 if (rs)
6048 goto ruleset_found;
6049
6050 rs = tcpcheck_ruleset_create("*smtp-check");
6051 if (rs == NULL) {
6052 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6053 goto error;
6054 }
6055
6056 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6057 1, curpx, &rs->rules, file, line, &errmsg);
6058 if (!chk) {
6059 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6060 goto error;
6061 }
6062 chk->index = 0;
6063 LIST_ADDQ(&rs->rules, &chk->list);
6064
6065 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
6066 "min-recv", "4",
6067 "error-status", "L7RSP",
6068 "on-error", "%[check.payload(),cut_crlf]",
6069 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006070 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006071 if (!chk) {
6072 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6073 goto error;
6074 }
6075 chk->index = 1;
6076 LIST_ADDQ(&rs->rules, &chk->list);
6077
6078 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6079 "min-recv", "4",
6080 "error-status", "L7STS",
6081 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6082 "status-code", "check.payload(0,3)",
6083 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006084 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006085 if (!chk) {
6086 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6087 goto error;
6088 }
6089 chk->index = 2;
6090 LIST_ADDQ(&rs->rules, &chk->list);
6091
6092 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6093 1, curpx, &rs->rules, file, line, &errmsg);
6094 if (!chk) {
6095 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6096 goto error;
6097 }
6098 chk->index = 3;
6099 LIST_ADDQ(&rs->rules, &chk->list);
6100
6101 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6102 "min-recv", "4",
6103 "error-status", "L7STS",
6104 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6105 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6106 "status-code", "check.payload(0,3)",
6107 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006108 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006109 if (!chk) {
6110 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6111 goto error;
6112 }
6113 chk->index = 4;
6114 LIST_ADDQ(&rs->rules, &chk->list);
6115
6116 LIST_ADDQ(&tcpchecks_list, &rs->list);
6117
6118 ruleset_found:
6119 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006120 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006121
6122 out:
6123 free(errmsg);
6124 return err_code;
6125
6126 error:
6127 free(cmd);
6128 free(var);
6129 free_tcpcheck_vars(&rules->preset_vars);
6130 tcpcheck_ruleset_release(rs);
6131 err_code |= ERR_ALERT | ERR_FATAL;
6132 goto out;
6133}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006134
Christopher Fauletce355072020-04-02 11:44:39 +02006135/* Parses the "option pgsql-check" proxy keyword */
6136int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6137 const char *file, int line)
6138{
6139 static char pgsql_req[] = {
6140 "%[var(check.plen),htonl,hex]" /* The packet length*/
6141 "00030000" /* the version 3.0 */
6142 "7573657200" /* "user" key */
6143 "%[var(check.username),hex]00" /* the username */
6144 "00"
6145 };
6146
6147 struct tcpcheck_ruleset *rs = NULL;
6148 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6149 struct tcpcheck_rule *chk;
6150 struct tcpcheck_var *var = NULL;
6151 char *user = NULL, *errmsg = NULL;
6152 size_t packetlen = 0;
6153 int err_code = 0;
6154
6155 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6156 err_code |= ERR_WARN;
6157
6158 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6159 goto out;
6160
Christopher Fauletce355072020-04-02 11:44:39 +02006161 curpx->options2 &= ~PR_O2_CHK_ANY;
6162 curpx->options2 |= PR_O2_TCPCHK_CHK;
6163
6164 free_tcpcheck_vars(&rules->preset_vars);
6165 rules->list = NULL;
6166 rules->flags = 0;
6167
6168 cur_arg += 2;
6169 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6170 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6171 file, line, args[0], args[1]);
6172 goto error;
6173 }
6174 if (strcmp(args[cur_arg], "user") == 0) {
6175 packetlen = 15 + strlen(args[cur_arg+1]);
6176 user = strdup(args[cur_arg+1]);
6177
6178 var = tcpcheck_var_create("check.username");
6179 if (user == NULL || var == NULL) {
6180 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6181 goto error;
6182 }
6183 var->data.type = SMP_T_STR;
6184 var->data.u.str.area = user;
6185 var->data.u.str.data = strlen(user);
6186 LIST_INIT(&var->list);
6187 LIST_ADDQ(&rules->preset_vars, &var->list);
6188 user = NULL;
6189 var = NULL;
6190
6191 var = tcpcheck_var_create("check.plen");
6192 if (var == NULL) {
6193 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6194 goto error;
6195 }
6196 var->data.type = SMP_T_SINT;
6197 var->data.u.sint = packetlen;
6198 LIST_INIT(&var->list);
6199 LIST_ADDQ(&rules->preset_vars, &var->list);
6200 var = NULL;
6201 }
6202 else {
6203 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6204 file, line, args[0], args[1]);
6205 goto error;
6206 }
6207
6208 rs = tcpcheck_ruleset_lookup("*pgsql-check");
6209 if (rs)
6210 goto ruleset_found;
6211
6212 rs = tcpcheck_ruleset_create("*pgsql-check");
6213 if (rs == NULL) {
6214 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6215 goto error;
6216 }
6217
6218 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6219 1, curpx, &rs->rules, file, line, &errmsg);
6220 if (!chk) {
6221 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6222 goto error;
6223 }
6224 chk->index = 0;
6225 LIST_ADDQ(&rs->rules, &chk->list);
6226
6227 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
6228 1, curpx, &rs->rules, file, line, &errmsg);
6229 if (!chk) {
6230 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6231 goto error;
6232 }
6233 chk->index = 1;
6234 LIST_ADDQ(&rs->rules, &chk->list);
6235
6236 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6237 "min-recv", "5",
6238 "error-status", "L7RSP",
6239 "on-error", "%[check.payload(6,0)]",
6240 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006241 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006242 if (!chk) {
6243 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6244 goto error;
6245 }
6246 chk->index = 2;
6247 LIST_ADDQ(&rs->rules, &chk->list);
6248
6249 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
6250 "min-recv", "9",
6251 "error-status", "L7STS",
6252 "on-success", "PostgreSQL server is ok",
6253 "on-error", "PostgreSQL unknown error",
6254 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006255 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006256 if (!chk) {
6257 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6258 goto error;
6259 }
6260 chk->index = 3;
6261 LIST_ADDQ(&rs->rules, &chk->list);
6262
6263 LIST_ADDQ(&tcpchecks_list, &rs->list);
6264
6265 ruleset_found:
6266 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006267 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006268
6269 out:
6270 free(errmsg);
6271 return err_code;
6272
6273 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006274 free(user);
6275 free(var);
6276 free_tcpcheck_vars(&rules->preset_vars);
6277 tcpcheck_ruleset_release(rs);
6278 err_code |= ERR_ALERT | ERR_FATAL;
6279 goto out;
6280}
6281
6282
6283/* Parses the "option mysql-check" proxy keyword */
6284int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6285 const char *file, int line)
6286{
6287 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6288 * const char mysql40_client_auth_pkt[] = {
6289 * "\x0e\x00\x00" // packet length
6290 * "\x01" // packet number
6291 * "\x00\x00" // client capabilities
6292 * "\x00\x00\x01" // max packet
6293 * "haproxy\x00" // username (null terminated string)
6294 * "\x00" // filler (always 0x00)
6295 * "\x01\x00\x00" // packet length
6296 * "\x00" // packet number
6297 * "\x01" // COM_QUIT command
6298 * };
6299 */
6300 static char mysql40_rsname[] = "*mysql40-check";
6301 static char mysql40_req[] = {
6302 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6303 "0080" /* client capabilities */
6304 "000001" /* max packet */
6305 "%[var(check.username),hex]00" /* the username */
6306 "00" /* filler (always 0x00) */
6307 "010000" /* packet length*/
6308 "00" /* sequence ID */
6309 "01" /* COM_QUIT command */
6310 };
6311
6312 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6313 * const char mysql41_client_auth_pkt[] = {
6314 * "\x0e\x00\x00\" // packet length
6315 * "\x01" // packet number
6316 * "\x00\x00\x00\x00" // client capabilities
6317 * "\x00\x00\x00\x01" // max packet
6318 * "\x21" // character set (UTF-8)
6319 * char[23] // All zeroes
6320 * "haproxy\x00" // username (null terminated string)
6321 * "\x00" // filler (always 0x00)
6322 * "\x01\x00\x00" // packet length
6323 * "\x00" // packet number
6324 * "\x01" // COM_QUIT command
6325 * };
6326 */
6327 static char mysql41_rsname[] = "*mysql41-check";
6328 static char mysql41_req[] = {
6329 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6330 "00820000" /* client capabilities */
6331 "00800001" /* max packet */
6332 "21" /* character set (UTF-8) */
6333 "000000000000000000000000" /* 23 bytes, al zeroes */
6334 "0000000000000000000000"
6335 "%[var(check.username),hex]00" /* the username */
6336 "00" /* filler (always 0x00) */
6337 "010000" /* packet length*/
6338 "00" /* sequence ID */
6339 "01" /* COM_QUIT command */
6340 };
6341
6342 struct tcpcheck_ruleset *rs = NULL;
6343 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6344 struct tcpcheck_rule *chk;
6345 struct tcpcheck_var *var = NULL;
6346 char *mysql_rsname = "*mysql-check";
6347 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6348 int index = 0, err_code = 0;
6349
6350 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6351 err_code |= ERR_WARN;
6352
6353 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6354 goto out;
6355
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006356 curpx->options2 &= ~PR_O2_CHK_ANY;
6357 curpx->options2 |= PR_O2_TCPCHK_CHK;
6358
6359 free_tcpcheck_vars(&rules->preset_vars);
6360 rules->list = NULL;
6361 rules->flags = 0;
6362
6363 cur_arg += 2;
6364 if (*args[cur_arg]) {
6365 char *user;
6366 int packetlen, userlen;
6367
6368 if (strcmp(args[cur_arg], "user") != 0) {
6369 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6370 file, line, args[0], args[1], args[cur_arg]);
6371 goto error;
6372 }
6373
6374 if (*(args[cur_arg+1]) == 0) {
6375 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6376 file, line, args[0], args[1], args[cur_arg]);
6377 goto error;
6378 }
6379
6380 hdr = calloc(4, sizeof(*hdr));
6381 user = strdup(args[cur_arg+1]);
6382 userlen = strlen(args[cur_arg+1]);
6383
6384 if (hdr == NULL || user == NULL) {
6385 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6386 goto error;
6387 }
6388
6389 if (*args[cur_arg+2]) {
6390 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6391 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6392 file, line, args[cur_arg], args[cur_arg+2]);
6393 goto error;
6394 }
6395 packetlen = userlen + 7 + 27;
6396 mysql_req = mysql41_req;
6397 mysql_rsname = mysql41_rsname;
6398 }
6399 else {
6400 packetlen = userlen + 7;
6401 mysql_req = mysql40_req;
6402 mysql_rsname = mysql40_rsname;
6403 }
6404
6405 hdr[0] = (unsigned char)(packetlen & 0xff);
6406 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6407 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6408 hdr[3] = 1;
6409
6410 var = tcpcheck_var_create("check.header");
6411 if (var == NULL) {
6412 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6413 goto error;
6414 }
6415 var->data.type = SMP_T_STR;
6416 var->data.u.str.area = hdr;
6417 var->data.u.str.data = 4;
6418 LIST_INIT(&var->list);
6419 LIST_ADDQ(&rules->preset_vars, &var->list);
6420 hdr = NULL;
6421 var = NULL;
6422
6423 var = tcpcheck_var_create("check.username");
6424 if (var == NULL) {
6425 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6426 goto error;
6427 }
6428 var->data.type = SMP_T_STR;
6429 var->data.u.str.area = user;
6430 var->data.u.str.data = strlen(user);
6431 LIST_INIT(&var->list);
6432 LIST_ADDQ(&rules->preset_vars, &var->list);
6433 user = NULL;
6434 var = NULL;
6435 }
6436
6437 rs = tcpcheck_ruleset_lookup(mysql_rsname);
6438 if (rs)
6439 goto ruleset_found;
6440
6441 rs = tcpcheck_ruleset_create(mysql_rsname);
6442 if (rs == NULL) {
6443 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6444 goto error;
6445 }
6446
6447 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6448 1, curpx, &rs->rules, file, line, &errmsg);
6449 if (!chk) {
6450 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6451 goto error;
6452 }
6453 chk->index = index++;
6454 LIST_ADDQ(&rs->rules, &chk->list);
6455
6456 if (mysql_req) {
6457 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6458 1, curpx, &rs->rules, file, line, &errmsg);
6459 if (!chk) {
6460 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6461 goto error;
6462 }
6463 chk->index = index++;
6464 LIST_ADDQ(&rs->rules, &chk->list);
6465 }
6466
6467 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006468 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006469 if (!chk) {
6470 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6471 goto error;
6472 }
6473 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6474 chk->index = index++;
6475 LIST_ADDQ(&rs->rules, &chk->list);
6476
6477 if (mysql_req) {
6478 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006479 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006480 if (!chk) {
6481 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6482 goto error;
6483 }
6484 chk->expect.custom = tcpcheck_mysql_expect_ok;
6485 chk->index = index++;
6486 LIST_ADDQ(&rs->rules, &chk->list);
6487 }
6488
6489 LIST_ADDQ(&tcpchecks_list, &rs->list);
6490
6491 ruleset_found:
6492 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006493 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006494
6495 out:
6496 free(errmsg);
6497 return err_code;
6498
6499 error:
6500 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006501 free(user);
6502 free(var);
6503 free_tcpcheck_vars(&rules->preset_vars);
6504 tcpcheck_ruleset_release(rs);
6505 err_code |= ERR_ALERT | ERR_FATAL;
6506 goto out;
6507}
6508
Christopher Faulet1997eca2020-04-03 23:13:50 +02006509int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6510 const char *file, int line)
6511{
6512 static char *ldap_req = "300C020101600702010304008000";
6513
6514 struct tcpcheck_ruleset *rs = NULL;
6515 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6516 struct tcpcheck_rule *chk;
6517 char *errmsg = NULL;
6518 int err_code = 0;
6519
6520 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6521 err_code |= ERR_WARN;
6522
6523 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6524 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006525
6526 curpx->options2 &= ~PR_O2_CHK_ANY;
6527 curpx->options2 |= PR_O2_TCPCHK_CHK;
6528
6529 free_tcpcheck_vars(&rules->preset_vars);
6530 rules->list = NULL;
6531 rules->flags = 0;
6532
6533 rs = tcpcheck_ruleset_lookup("*ldap-check");
6534 if (rs)
6535 goto ruleset_found;
6536
6537 rs = tcpcheck_ruleset_create("*ldap-check");
6538 if (rs == NULL) {
6539 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6540 goto error;
6541 }
6542
6543 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6544 1, curpx, &rs->rules, file, line, &errmsg);
6545 if (!chk) {
6546 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6547 goto error;
6548 }
6549 chk->index = 0;
6550 LIST_ADDQ(&rs->rules, &chk->list);
6551
6552 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6553 "min-recv", "14",
6554 "on-error", "Not LDAPv3 protocol",
6555 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006556 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006557 if (!chk) {
6558 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6559 goto error;
6560 }
6561 chk->index = 1;
6562 LIST_ADDQ(&rs->rules, &chk->list);
6563
6564 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006565 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006566 if (!chk) {
6567 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6568 goto error;
6569 }
6570 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6571 chk->index = 2;
6572 LIST_ADDQ(&rs->rules, &chk->list);
6573
6574 LIST_ADDQ(&tcpchecks_list, &rs->list);
6575
6576 ruleset_found:
6577 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006578 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006579
6580 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006581 free(errmsg);
6582 return err_code;
6583
6584 error:
6585 tcpcheck_ruleset_release(rs);
6586 err_code |= ERR_ALERT | ERR_FATAL;
6587 goto out;
6588}
6589
6590int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6591 const char *file, int line)
6592{
6593 struct tcpcheck_ruleset *rs = NULL;
6594 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6595 struct tcpcheck_rule *chk;
6596 char *spop_req = NULL;
6597 char *errmsg = NULL;
6598 int spop_len = 0, err_code = 0;
6599
6600 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6601 err_code |= ERR_WARN;
6602
6603 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6604 goto out;
6605
Christopher Faulet267b01b2020-04-04 10:27:09 +02006606 curpx->options2 &= ~PR_O2_CHK_ANY;
6607 curpx->options2 |= PR_O2_TCPCHK_CHK;
6608
6609 free_tcpcheck_vars(&rules->preset_vars);
6610 rules->list = NULL;
6611 rules->flags = 0;
6612
6613
6614 rs = tcpcheck_ruleset_lookup("*spop-check");
6615 if (rs)
6616 goto ruleset_found;
6617
6618 rs = tcpcheck_ruleset_create("*spop-check");
6619 if (rs == NULL) {
6620 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6621 goto error;
6622 }
6623
6624 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6625 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6626 goto error;
6627 }
6628 chunk_reset(&trash);
6629 dump_binary(&trash, spop_req, spop_len);
6630 trash.area[trash.data] = '\0';
6631
6632 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6633 1, curpx, &rs->rules, file, line, &errmsg);
6634 if (!chk) {
6635 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6636 goto error;
6637 }
6638 chk->index = 0;
6639 LIST_ADDQ(&rs->rules, &chk->list);
6640
6641 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006642 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006643 if (!chk) {
6644 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6645 goto error;
6646 }
6647 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6648 chk->index = 1;
6649 LIST_ADDQ(&rs->rules, &chk->list);
6650
6651 LIST_ADDQ(&tcpchecks_list, &rs->list);
6652
6653 ruleset_found:
6654 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006655 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006656
6657 out:
6658 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006659 free(errmsg);
6660 return err_code;
6661
6662 error:
6663 tcpcheck_ruleset_release(rs);
6664 err_code |= ERR_ALERT | ERR_FATAL;
6665 goto out;
6666}
Christopher Fauletce355072020-04-02 11:44:39 +02006667
Christopher Faulete5870d82020-04-15 11:32:03 +02006668
6669struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6670{
6671 struct tcpcheck_rule *chk = NULL;
6672 struct tcpcheck_http_hdr *hdr = NULL;
6673 char *meth = NULL, *uri = NULL, *vsn = NULL;
6674 char *hdrs, *body;
6675
6676 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6677 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6678 if (hdrs == body)
6679 hdrs = NULL;
6680 if (hdrs) {
6681 *hdrs = '\0';
6682 hdrs +=2;
6683 }
6684 if (body) {
6685 *body = '\0';
6686 body += 4;
6687 }
6688 if (hdrs || body) {
6689 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6690 " Please, consider to use 'http-check send' directive instead.");
6691 }
6692
6693 chk = calloc(1, sizeof(*chk));
6694 if (!chk) {
6695 memprintf(errmsg, "out of memory");
6696 goto error;
6697 }
6698 chk->action = TCPCHK_ACT_SEND;
6699 chk->send.type = TCPCHK_SEND_HTTP;
6700 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6701 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6702 LIST_INIT(&chk->send.http.hdrs);
6703
6704 /* Copy the method, uri and version */
6705 if (*args[cur_arg]) {
6706 if (!*args[cur_arg+1])
6707 uri = args[cur_arg];
6708 else
6709 meth = args[cur_arg];
6710 }
6711 if (*args[cur_arg+1])
6712 uri = args[cur_arg+1];
6713 if (*args[cur_arg+2])
6714 vsn = args[cur_arg+2];
6715
6716 if (meth) {
6717 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6718 chk->send.http.meth.str.area = strdup(meth);
6719 chk->send.http.meth.str.data = strlen(meth);
6720 if (!chk->send.http.meth.str.area) {
6721 memprintf(errmsg, "out of memory");
6722 goto error;
6723 }
6724 }
6725 if (uri) {
6726 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
6727 if (!chk->send.http.uri.ptr) {
6728 memprintf(errmsg, "out of memory");
6729 goto error;
6730 }
6731 }
6732 if (vsn) {
6733 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
6734 if (!chk->send.http.vsn.ptr) {
6735 memprintf(errmsg, "out of memory");
6736 goto error;
6737 }
6738 }
6739
6740 /* Copy the header */
6741 if (hdrs) {
6742 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
6743 struct h1m h1m;
6744 int i, ret;
6745
6746 /* Build and parse the request */
6747 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
6748
6749 h1m.flags = H1_MF_HDRS_ONLY;
6750 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
6751 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
6752 &h1m, NULL);
6753 if (ret <= 0) {
6754 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
6755 goto error;
6756 }
6757
6758 for (i = 0; tmp_hdrs[i].n.len; i++) {
6759 hdr = calloc(1, sizeof(*hdr));
6760 if (!hdr) {
6761 memprintf(errmsg, "out of memory");
6762 goto error;
6763 }
6764 LIST_INIT(&hdr->value);
6765 hdr->name = ist2(strdup(tmp_hdrs[i].n.ptr), tmp_hdrs[i].n.len);
6766 if (!hdr->name.ptr) {
6767 memprintf(errmsg, "out of memory");
6768 goto error;
6769 }
6770
6771 tmp_hdrs[i].v.ptr[tmp_hdrs[i].v.len] = '\0';
6772 if (!parse_logformat_string(tmp_hdrs[i].v.ptr, px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
6773 goto error;
6774 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
6775 }
6776 }
6777
6778 /* Copy the body */
6779 if (body) {
6780 chk->send.http.body = ist2(strdup(body), strlen(body));
6781 if (!chk->send.http.body.ptr) {
6782 memprintf(errmsg, "out of memory");
6783 goto error;
6784 }
6785 }
6786
6787 return chk;
6788
6789 error:
6790 free_tcpcheck_http_hdr(hdr);
6791 free_tcpcheck(chk, 0);
6792 return NULL;
6793}
6794
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006795int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6796 const char *file, int line)
6797{
Christopher Faulete5870d82020-04-15 11:32:03 +02006798 struct tcpcheck_ruleset *rs = NULL;
6799 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6800 struct tcpcheck_rule *chk;
6801 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006802 int err_code = 0;
6803
6804 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6805 err_code |= ERR_WARN;
6806
6807 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6808 goto out;
6809
Christopher Faulete5870d82020-04-15 11:32:03 +02006810 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
6811 if (!chk) {
6812 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6813 goto error;
6814 }
6815 if (errmsg) {
6816 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
6817 err_code |= ERR_WARN;
6818 free(errmsg);
6819 errmsg = NULL;
6820 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006821
Christopher Faulete5870d82020-04-15 11:32:03 +02006822 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006823 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02006824 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006825
Christopher Faulete5870d82020-04-15 11:32:03 +02006826 free_tcpcheck_vars(&rules->preset_vars);
6827 rules->list = NULL;
6828 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006829
Christopher Faulete5870d82020-04-15 11:32:03 +02006830 /* Deduce the ruleset name from the proxy info */
6831 chunk_printf(&trash, "*http-check-%s_%s-%d",
6832 ((curpx == defpx) ? "defaults" : curpx->id),
6833 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006834
Christopher Faulete5870d82020-04-15 11:32:03 +02006835 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
6836 if (rs == NULL) {
6837 rs = tcpcheck_ruleset_create(b_orig(&trash));
6838 if (rs == NULL) {
6839 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6840 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006841 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006842 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006843
Christopher Faulete5870d82020-04-15 11:32:03 +02006844 rules->list = &rs->rules;
6845 rules->flags |= TCPCHK_RULES_HTTP_CHK;
6846 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
6847 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6848 rules->list = NULL;
6849 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006850 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006851
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006852 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02006853 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006854 return err_code;
6855
6856 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02006857 tcpcheck_ruleset_release(rs);
6858 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006859 err_code |= ERR_ALERT | ERR_FATAL;
6860 goto out;
6861}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006862
Christopher Faulet6f557912020-04-09 15:58:50 +02006863int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6864 const char *file, int line)
6865{
6866 int err_code = 0;
6867
Christopher Faulet6f557912020-04-09 15:58:50 +02006868 curpx->options2 &= ~PR_O2_CHK_ANY;
6869 curpx->options2 |= PR_O2_EXT_CHK;
6870 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6871 goto out;
6872
6873 out:
6874 return err_code;
6875}
6876
Christopher Fauletce8111e2020-04-06 15:04:11 +02006877/* Parse the "addr" server keyword */
6878static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6879 char **errmsg)
6880{
6881 struct sockaddr_storage *sk;
6882 struct protocol *proto;
6883 int port1, port2, err_code = 0;
6884
6885
6886 if (!*args[*cur_arg+1]) {
6887 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6888 goto error;
6889 }
6890
6891 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6892 if (!sk) {
6893 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6894 goto error;
6895 }
6896
6897 proto = protocol_by_family(sk->ss_family);
6898 if (!proto || !proto->connect) {
6899 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6900 args[*cur_arg], args[*cur_arg+1]);
6901 goto error;
6902 }
6903
6904 if (port1 != port2) {
6905 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6906 args[*cur_arg], args[*cur_arg+1]);
6907 goto error;
6908 }
6909
6910 srv->check.addr = srv->agent.addr = *sk;
6911 srv->flags |= SRV_F_CHECKADDR;
6912 srv->flags |= SRV_F_AGENTADDR;
6913
6914 out:
6915 return err_code;
6916
6917 error:
6918 err_code |= ERR_ALERT | ERR_FATAL;
6919 goto out;
6920}
6921
6922
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006923/* Parse the "agent-addr" server keyword */
6924static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6925 char **errmsg)
6926{
6927 int err_code = 0;
6928
6929 if (!*(args[*cur_arg+1])) {
6930 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6931 goto error;
6932 }
6933 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6934 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6935 goto error;
6936 }
6937
6938 out:
6939 return err_code;
6940
6941 error:
6942 err_code |= ERR_ALERT | ERR_FATAL;
6943 goto out;
6944}
6945
6946/* Parse the "agent-check" server keyword */
6947static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6948 char **errmsg)
6949{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006950 struct tcpcheck_ruleset *rs = NULL;
6951 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6952 struct tcpcheck_rule *chk;
6953 int err_code = 0;
6954
6955 if (srv->do_agent)
6956 goto out;
6957
6958 if (!rules) {
6959 rules = calloc(1, sizeof(*rules));
6960 if (!rules) {
6961 memprintf(errmsg, "out of memory.");
6962 goto error;
6963 }
6964 LIST_INIT(&rules->preset_vars);
6965 srv->agent.tcpcheck_rules = rules;
6966 }
6967 rules->list = NULL;
6968 rules->flags = 0;
6969
6970 rs = tcpcheck_ruleset_lookup("*agent-check");
6971 if (rs)
6972 goto ruleset_found;
6973
6974 rs = tcpcheck_ruleset_create("*agent-check");
6975 if (rs == NULL) {
6976 memprintf(errmsg, "out of memory.");
6977 goto error;
6978 }
6979
6980 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6981 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6982 if (!chk) {
6983 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6984 goto error;
6985 }
6986 chk->index = 0;
6987 LIST_ADDQ(&rs->rules, &chk->list);
6988
6989 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006990 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
6991 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006992 if (!chk) {
6993 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6994 goto error;
6995 }
6996 chk->expect.custom = tcpcheck_agent_expect_reply;
6997 chk->index = 1;
6998 LIST_ADDQ(&rs->rules, &chk->list);
6999
7000 LIST_ADDQ(&tcpchecks_list, &rs->list);
7001
7002 ruleset_found:
7003 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02007004 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007005 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007006
7007 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007008 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007009
7010 error:
7011 deinit_srv_agent_check(srv);
7012 tcpcheck_ruleset_release(rs);
7013 err_code |= ERR_ALERT | ERR_FATAL;
7014 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007015}
7016
7017/* Parse the "agent-inter" server keyword */
7018static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7019 char **errmsg)
7020{
7021 const char *err = NULL;
7022 unsigned int delay;
7023 int err_code = 0;
7024
7025 if (!*(args[*cur_arg+1])) {
7026 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7027 goto error;
7028 }
7029
7030 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7031 if (err == PARSE_TIME_OVER) {
7032 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7033 args[*cur_arg+1], args[*cur_arg], srv->id);
7034 goto error;
7035 }
7036 else if (err == PARSE_TIME_UNDER) {
7037 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7038 args[*cur_arg+1], args[*cur_arg], srv->id);
7039 goto error;
7040 }
7041 else if (err) {
7042 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7043 *err, srv->id);
7044 goto error;
7045 }
7046 if (delay <= 0) {
7047 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7048 delay, args[*cur_arg], srv->id);
7049 goto error;
7050 }
7051 srv->agent.inter = delay;
7052
7053 out:
7054 return err_code;
7055
7056 error:
7057 err_code |= ERR_ALERT | ERR_FATAL;
7058 goto out;
7059}
7060
7061/* Parse the "agent-port" server keyword */
7062static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7063 char **errmsg)
7064{
7065 int err_code = 0;
7066
7067 if (!*(args[*cur_arg+1])) {
7068 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7069 goto error;
7070 }
7071
7072 global.maxsock++;
7073 srv->agent.port = atol(args[*cur_arg+1]);
7074
7075 out:
7076 return err_code;
7077
7078 error:
7079 err_code |= ERR_ALERT | ERR_FATAL;
7080 goto out;
7081}
7082
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007083int set_srv_agent_send(struct server *srv, const char *send)
7084{
7085 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7086 struct tcpcheck_var *var = NULL;
7087 char *str;
7088
7089 str = strdup(send);
7090 var = tcpcheck_var_create("check.agent_string");
7091 if (str == NULL || var == NULL)
7092 goto error;
7093
7094 free_tcpcheck_vars(&rules->preset_vars);
7095
7096 var->data.type = SMP_T_STR;
7097 var->data.u.str.area = str;
7098 var->data.u.str.data = strlen(str);
7099 LIST_INIT(&var->list);
7100 LIST_ADDQ(&rules->preset_vars, &var->list);
7101
7102 return 1;
7103
7104 error:
7105 free(str);
7106 free(var);
7107 return 0;
7108}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007109
7110/* Parse the "agent-send" server keyword */
7111static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7112 char **errmsg)
7113{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007114 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007115 int err_code = 0;
7116
7117 if (!*(args[*cur_arg+1])) {
7118 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7119 goto error;
7120 }
7121
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007122 if (!rules) {
7123 rules = calloc(1, sizeof(*rules));
7124 if (!rules) {
7125 memprintf(errmsg, "out of memory.");
7126 goto error;
7127 }
7128 LIST_INIT(&rules->preset_vars);
7129 srv->agent.tcpcheck_rules = rules;
7130 }
7131
7132 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007133 memprintf(errmsg, "out of memory.");
7134 goto error;
7135 }
7136
7137 out:
7138 return err_code;
7139
7140 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007141 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007142 err_code |= ERR_ALERT | ERR_FATAL;
7143 goto out;
7144}
7145
7146/* Parse the "no-agent-send" server keyword */
7147static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7148 char **errmsg)
7149{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007150 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007151 return 0;
7152}
7153
Christopher Fauletce8111e2020-04-06 15:04:11 +02007154/* Parse the "check" server keyword */
7155static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7156 char **errmsg)
7157{
7158 srv->do_check = 1;
7159 return 0;
7160}
7161
7162/* Parse the "check-send-proxy" server keyword */
7163static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7164 char **errmsg)
7165{
7166 srv->check.send_proxy = 1;
7167 return 0;
7168}
7169
7170/* Parse the "check-via-socks4" server keyword */
7171static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7172 char **errmsg)
7173{
7174 srv->check.via_socks4 = 1;
7175 return 0;
7176}
7177
7178/* Parse the "no-check" server keyword */
7179static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7180 char **errmsg)
7181{
7182 deinit_srv_check(srv);
7183 return 0;
7184}
7185
7186/* Parse the "no-check-send-proxy" server keyword */
7187static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7188 char **errmsg)
7189{
7190 srv->check.send_proxy = 0;
7191 return 0;
7192}
7193
7194/* Parse the "rise" server keyword */
7195static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7196 char **errmsg)
7197{
7198 int err_code = 0;
7199
7200 if (!*args[*cur_arg + 1]) {
7201 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7202 goto error;
7203 }
7204
7205 srv->check.rise = atol(args[*cur_arg+1]);
7206 if (srv->check.rise <= 0) {
7207 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7208 goto error;
7209 }
7210
7211 if (srv->check.health)
7212 srv->check.health = srv->check.rise;
7213
7214 out:
7215 return err_code;
7216
7217 error:
7218 deinit_srv_agent_check(srv);
7219 err_code |= ERR_ALERT | ERR_FATAL;
7220 goto out;
7221 return 0;
7222}
7223
7224/* Parse the "fall" server keyword */
7225static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7226 char **errmsg)
7227{
7228 int err_code = 0;
7229
7230 if (!*args[*cur_arg + 1]) {
7231 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7232 goto error;
7233 }
7234
7235 srv->check.fall = atol(args[*cur_arg+1]);
7236 if (srv->check.fall <= 0) {
7237 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7238 goto error;
7239 }
7240
7241 out:
7242 return err_code;
7243
7244 error:
7245 deinit_srv_agent_check(srv);
7246 err_code |= ERR_ALERT | ERR_FATAL;
7247 goto out;
7248 return 0;
7249}
7250
7251/* Parse the "inter" server keyword */
7252static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7253 char **errmsg)
7254{
7255 const char *err = NULL;
7256 unsigned int delay;
7257 int err_code = 0;
7258
7259 if (!*(args[*cur_arg+1])) {
7260 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7261 goto error;
7262 }
7263
7264 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7265 if (err == PARSE_TIME_OVER) {
7266 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7267 args[*cur_arg+1], args[*cur_arg], srv->id);
7268 goto error;
7269 }
7270 else if (err == PARSE_TIME_UNDER) {
7271 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7272 args[*cur_arg+1], args[*cur_arg], srv->id);
7273 goto error;
7274 }
7275 else if (err) {
7276 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7277 *err, srv->id);
7278 goto error;
7279 }
7280 if (delay <= 0) {
7281 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7282 delay, args[*cur_arg], srv->id);
7283 goto error;
7284 }
7285 srv->check.inter = delay;
7286
7287 out:
7288 return err_code;
7289
7290 error:
7291 err_code |= ERR_ALERT | ERR_FATAL;
7292 goto out;
7293}
7294
7295
7296/* Parse the "fastinter" server keyword */
7297static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7298 char **errmsg)
7299{
7300 const char *err = NULL;
7301 unsigned int delay;
7302 int err_code = 0;
7303
7304 if (!*(args[*cur_arg+1])) {
7305 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7306 goto error;
7307 }
7308
7309 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7310 if (err == PARSE_TIME_OVER) {
7311 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7312 args[*cur_arg+1], args[*cur_arg], srv->id);
7313 goto error;
7314 }
7315 else if (err == PARSE_TIME_UNDER) {
7316 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7317 args[*cur_arg+1], args[*cur_arg], srv->id);
7318 goto error;
7319 }
7320 else if (err) {
7321 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7322 *err, srv->id);
7323 goto error;
7324 }
7325 if (delay <= 0) {
7326 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7327 delay, args[*cur_arg], srv->id);
7328 goto error;
7329 }
7330 srv->check.fastinter = delay;
7331
7332 out:
7333 return err_code;
7334
7335 error:
7336 err_code |= ERR_ALERT | ERR_FATAL;
7337 goto out;
7338}
7339
7340
7341/* Parse the "downinter" server keyword */
7342static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7343 char **errmsg)
7344{
7345 const char *err = NULL;
7346 unsigned int delay;
7347 int err_code = 0;
7348
7349 if (!*(args[*cur_arg+1])) {
7350 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7351 goto error;
7352 }
7353
7354 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7355 if (err == PARSE_TIME_OVER) {
7356 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7357 args[*cur_arg+1], args[*cur_arg], srv->id);
7358 goto error;
7359 }
7360 else if (err == PARSE_TIME_UNDER) {
7361 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7362 args[*cur_arg+1], args[*cur_arg], srv->id);
7363 goto error;
7364 }
7365 else if (err) {
7366 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7367 *err, srv->id);
7368 goto error;
7369 }
7370 if (delay <= 0) {
7371 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7372 delay, args[*cur_arg], srv->id);
7373 goto error;
7374 }
7375 srv->check.downinter = delay;
7376
7377 out:
7378 return err_code;
7379
7380 error:
7381 err_code |= ERR_ALERT | ERR_FATAL;
7382 goto out;
7383}
7384
7385/* Parse the "port" server keyword */
7386static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7387 char **errmsg)
7388{
7389 int err_code = 0;
7390
7391 if (!*(args[*cur_arg+1])) {
7392 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7393 goto error;
7394 }
7395
7396 global.maxsock++;
7397 srv->check.port = atol(args[*cur_arg+1]);
7398 srv->flags |= SRV_F_CHECKPORT;
7399
7400 out:
7401 return err_code;
7402
7403 error:
7404 err_code |= ERR_ALERT | ERR_FATAL;
7405 goto out;
7406}
7407
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007408static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007409 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7410 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7411 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007412 { 0, NULL, NULL },
7413}};
7414
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007415static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007416 { "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 +02007417 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7418 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7419 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7420 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7421 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007422 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
7423 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7424 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007425 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007426 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7427 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7428 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7429 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7430 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7431 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7432 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7433 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007434 { NULL, NULL, 0 },
7435}};
7436
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007437INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007438INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007439
Willy Tarreaubd741542010-03-16 18:46:54 +01007440/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007441 * Local variables:
7442 * c-indent-level: 8
7443 * c-basic-offset: 8
7444 * End:
7445 */