blob: 41927204a37db506d9a7bd6ec944cff848068f08 [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
2996static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
2997{
2998 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2999 struct tcpcheck_expect *expect = &rule->expect;
3000 struct buffer *msg = NULL;
3001 enum healthcheck_status status;
3002 struct ist desc = ist(NULL);
3003 char *body;
3004 size_t body_len;
3005 int match, inverse;
3006
3007 last_read |= b_full(&check->bi);
3008
3009 /* Must at least receive the status line (HTTP/1.X XXX.) */
3010 if (!last_read && b_data(&check->bi) < 13)
3011 goto wait_more_data;
3012
3013 /* Check if the server speaks HTTP 1.X */
3014 if (b_data(&check->bi) < 13 ||
3015 memcmp(b_head(&check->bi), "HTTP/1.", 7) != 0 ||
3016 (*b_peek(&check->bi, 12) != ' ' && *b_peek(&check->bi, 12) != '\r') ||
3017 !isdigit((unsigned char) *b_peek(&check->bi, 9)) || !isdigit((unsigned char) *b_peek(&check->bi, 10)) ||
3018 !isdigit((unsigned char) *b_peek(&check->bi, 11))) {
3019 status = HCHK_STATUS_L7RSP;
3020 desc = ist2(b_head(&check->bi), my_memcspn(b_head(&check->bi), b_data(&check->bi), "\r\n", 2));
3021 goto error;
3022 }
3023
3024 check->code = strl2uic(b_peek(&check->bi, 9), 3);
3025
3026 if (check->server &&
3027 (check->server->proxy->options & PR_O_DISABLE404) &&
3028 (check->server->next_state != SRV_ST_STOPPED) &&
3029 (check->code == 404)) {
3030 /* 404 may be accepted as "stopping" only if the server was up */
3031 goto out;
3032 }
3033
3034 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
3035 /* Make GCC happy ; initialize match to a failure state. */
3036 match = inverse;
3037
3038 switch (expect->type) {
3039 case TCPCHK_EXPECT_HTTP_STATUS:
3040 match = my_memmem(b_peek(&check->bi, 9), 3, expect->data.ptr, istlen(expect->data)) != NULL;
3041
3042 /* Set status and description in case of error */
3043 status = HCHK_STATUS_L7STS;
3044 desc = ist2(b_peek(&check->bi, 12), my_memcspn(b_peek(&check->bi, 12), b_data(&check->bi) - 12, "\r\n", 2));
3045 break;
3046 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
3047 match = regex_exec2(expect->regex, b_peek(&check->bi, 9), 3);
3048
3049 /* Set status and description in case of error */
3050 status = HCHK_STATUS_L7STS;
3051 desc = ist2(b_peek(&check->bi, 12), my_memcspn(b_peek(&check->bi, 12), b_data(&check->bi) - 12, "\r\n", 2));
3052 break;
3053 case TCPCHK_EXPECT_HTTP_BODY:
3054 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
3055 body = (char *)my_memmem(b_head(&check->bi), b_data(&check->bi), "\r\n\r\n", 4);
3056 if (!body) {
3057 if (!last_read)
3058 goto wait_more_data;
3059
3060 status = HCHK_STATUS_L7RSP;
3061 desc = ist("HTTP content check could not find a response body");
3062 goto error;
3063 }
3064 body += 4;
3065 body_len = b_tail(&check->bi) - body;
3066
3067 if (!last_read &&
3068 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && body_len < istlen(expect->data)) ||
3069 (expect->min_recv > 0 && body_len < expect->min_recv))) {
3070 ret = TCPCHK_EVAL_WAIT;
3071 goto out;
3072 }
3073
3074 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
3075 match = my_memmem(body, body_len, expect->data.ptr, istlen(expect->data)) != NULL;
3076 else
3077 match = regex_exec2(expect->regex, body, body_len);
3078
3079 /* Set status and description in case of error */
3080 status = HCHK_STATUS_L7RSP;
3081 desc = (inverse
3082 ? ist("HTTP check matched unwanted content")
3083 : ist("HTTP content check did not match"));
3084 break;
3085
3086 default:
3087 /* should never happen */
3088 status = HCHK_STATUS_L7RSP;
3089 goto error;
3090 }
3091
3092 /* Wait for more data on mismatch only if no minimum is defined (-1),
3093 * otherwise the absence of match is already conclusive.
3094 */
3095 if (!match && !last_read && (expect->min_recv == -1)) {
3096 ret = TCPCHK_EVAL_WAIT;
3097 goto out;
3098 }
3099
3100 if (!(match ^ inverse))
3101 goto error;
3102
3103 out:
3104 free_trash_chunk(msg);
3105 return ret;
3106
3107 error:
3108 ret = TCPCHK_EVAL_STOP;
3109 msg = alloc_trash_chunk();
3110 if (msg)
3111 tcpcheck_onerror_message(msg, check, rule, 0, desc);
3112 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003113 goto out;
3114
Christopher Faulete5870d82020-04-15 11:32:03 +02003115 wait_more_data:
3116 ret = TCPCHK_EVAL_WAIT;
3117 goto out;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003118}
Gaetan Rivetf8ba6772020-02-07 15:37:17 +01003119
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003120/* Evaluate a TCPCHK_ACT_EXPECT rule. It returns 1 to evaluate the next rule, 0
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003121 * to wait and -1 to stop the check.
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003122 */
3123static enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
3124{
3125 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
Christopher Fauletec07e382020-04-07 14:56:26 +02003126 struct tcpcheck_expect *expect = &rule->expect;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003127 struct buffer *msg = NULL;
Christopher Faulet12d57402020-04-10 09:58:42 +02003128 int match, inverse;
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003129
Christopher Faulete5870d82020-04-15 11:32:03 +02003130 last_read |= b_full(&check->bi);
3131
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003132 /* The current expect might need more data than the previous one, check again
3133 * that the minimum amount data required to match is respected.
3134 */
3135 if (!last_read) {
3136 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003137 (b_data(&check->bi) < istlen(expect->data))) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003138 ret = TCPCHK_EVAL_WAIT;
3139 goto out;
3140 }
3141 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
3142 ret = TCPCHK_EVAL_WAIT;
3143 goto out;
3144 }
3145 }
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003146
Christopher Faulet12d57402020-04-10 09:58:42 +02003147 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003148 /* Make GCC happy ; initialize match to a failure state. */
Christopher Faulet12d57402020-04-10 09:58:42 +02003149 match = inverse;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003150
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003151 switch (expect->type) {
3152 case TCPCHK_EXPECT_STRING:
3153 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003154 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 +02003155 break;
3156 case TCPCHK_EXPECT_REGEX:
Christopher Faulet12d57402020-04-10 09:58:42 +02003157 if (expect->flags & TCPCHK_EXPT_FL_CAP)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003158 match = regex_exec_match2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1),
3159 MAX_MATCH, pmatch, 0);
3160 else
3161 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
3162 break;
Willy Tarreau7df8ca62019-07-15 10:57:51 +02003163
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003164 case TCPCHK_EXPECT_REGEX_BINARY:
3165 chunk_reset(&trash);
3166 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
Christopher Faulet12d57402020-04-10 09:58:42 +02003167 if (expect->flags & TCPCHK_EXPT_FL_CAP)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003168 match = regex_exec_match2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1),
3169 MAX_MATCH, pmatch, 0);
3170 else
3171 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
3172 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003173 case TCPCHK_EXPECT_CUSTOM:
3174 if (expect->custom)
3175 ret = expect->custom(check, rule, last_read);
3176 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02003177 default:
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003178 /* Should never happen. */
3179 ret = TCPCHK_EVAL_STOP;
3180 goto out;
3181 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003182
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003183
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003184 /* Wait for more data on mismatch only if no minimum is defined (-1),
3185 * otherwise the absence of match is already conclusive.
3186 */
3187 if (!match && !last_read && (expect->min_recv == -1)) {
3188 ret = TCPCHK_EVAL_WAIT;
3189 goto out;
3190 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003191
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003192 /* Result as expected, next rule. */
Christopher Faulet12d57402020-04-10 09:58:42 +02003193 if (match ^ inverse)
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003194 goto out;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003195
Willy Tarreaufbe0edf2013-12-06 16:54:31 +01003196
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003197 /* From this point on, we matched something we did not want, this is an error state. */
3198 ret = TCPCHK_EVAL_STOP;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003199 msg = alloc_trash_chunk();
Christopher Faulet206368d2020-04-03 14:51:06 +02003200 if (msg)
3201 tcpcheck_onerror_message(msg, check, rule, match, ist(NULL));
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003202 set_server_check_status(check, expect->err_status, (msg ? b_head(msg) : NULL));
Christopher Faulet206368d2020-04-03 14:51:06 +02003203 free_trash_chunk(msg);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003204 ret = TCPCHK_EVAL_STOP;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003205
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003206 out:
3207 return ret;
3208}
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003209
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003210/* Evaluate a TCPCHK_ACT_ACTION_KW rule. It returns 1 to evaluate the next rule, 0
3211 * to wait and -1 to stop the check.
3212 */
3213static enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
3214{
3215 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
3216 struct act_rule *act_rule;
3217 enum act_return act_ret;
3218
3219 act_rule =rule->action_kw.rule;
3220 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
3221 if (act_ret != ACT_RET_CONT) {
3222 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
3223 tcpcheck_get_step_id(check, rule));
3224 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
3225 ret = TCPCHK_EVAL_STOP;
3226 }
3227
3228 return ret;
3229}
3230
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003231/* proceed with next steps for the TCP checks <check>. Note that this is called
3232 * both from the connection's wake() callback and from the check scheduling task.
3233 * It returns 0 on normal cases, or <0 if a close() has happened on an existing
3234 * connection, presenting the risk of an fd replacement.
3235 *
3236 * Please do NOT place any return statement in this function and only leave
3237 * via the out_end_tcpcheck label after setting retcode.
3238 */
3239static int tcpcheck_main(struct check *check)
3240{
3241 struct tcpcheck_rule *rule;
3242 struct conn_stream *cs = check->cs;
3243 struct connection *conn = cs_conn(cs);
3244 int must_read = 1, last_read = 0;
3245 int ret, retcode = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003246
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003247 /* here, we know that the check is complete or that it failed */
3248 if (check->result != CHK_RES_UNKNOWN)
3249 goto out_end_tcpcheck;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003250
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003251 /* 1- check for connection error, if any */
3252 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3253 goto out_end_tcpcheck;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003254
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003255 /* 2- check if we are waiting for the connection establishment. It only
3256 * happens during TCPCHK_ACT_CONNECT. */
3257 if (conn && (conn->flags & CO_FL_WAIT_XPRT))
3258 goto out;
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003259
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003260 /* 3- check for pending outgoing data. It only happens during TCPCHK_ACT_SEND. */
3261 if (conn && b_data(&check->bo)) {
3262 ret = conn->mux->snd_buf(cs, &check->bo, b_data(&check->bo), 0);
3263 if (ret <= 0) {
3264 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
3265 goto out_end_tcpcheck;
3266 goto out;
3267 }
3268 if (b_data(&check->bo)) {
3269 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
3270 goto out;
3271 }
3272 }
Gaetan Rivetefab6c62020-02-07 15:37:17 +01003273
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003274 /* Now evaluate the tcp-check rules */
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003275
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003276 /* If check->current_step is defined, we are in resume condition. For
3277 * TCPCHK_ACT_CONNECT and TCPCHK_ACT_SEND rules, we must go to the next
3278 * rule before resuming the evaluation. For TCPCHK_ACT_EXPECT, we
3279 * re-evaluate the current rule. Others cannot yield.
3280 */
3281 if (check->current_step) {
3282 if (check->current_step->action == TCPCHK_ACT_CONNECT ||
3283 check->current_step->action == TCPCHK_ACT_SEND)
3284 rule = LIST_NEXT(&check->current_step->list, typeof(rule), list);
3285 else
3286 rule = check->current_step;
3287 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003288 else {
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003289 struct tcpcheck_var *var;
3290
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003291 /* First evaluation, create a session */
Gaetan Rivet13a50432020-02-21 18:13:44 +01003292 check->sess = session_new(&checks_fe, NULL, (check->server ? &check->server->obj_type : NULL));
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003293 if (!check->sess) {
3294 chunk_printf(&trash, "TCPCHK error allocating check session");
3295 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
3296 goto out_end_tcpcheck;
3297 }
Gaetan Rivet13a50432020-02-21 18:13:44 +01003298 vars_init(&check->vars, SCOPE_CHECK);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003299 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003300
3301 /* Preset tcp-check variables */
3302 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
3303 struct sample smp;
3304
3305 memset(&smp, 0, sizeof(smp));
3306 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
3307 smp.data = var->data;
3308 vars_set_by_name_ifexist(var->name.ptr, var->name.len, &smp);
3309 }
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003310 }
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003311
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003312 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003313 enum tcpcheck_eval_ret eval_ret;
Willy Tarreauf2c87352015-05-13 12:08:21 +02003314
Christopher Faulete5870d82020-04-15 11:32:03 +02003315 check->code = 0;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003316 switch (rule->action) {
3317 case TCPCHK_ACT_CONNECT:
3318 check->current_step = rule;
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003319
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003320 /* close but not release yet previous connection */
3321 if (check->cs) {
3322 cs_close(check->cs);
3323 retcode = -1; /* do not reuse the fd in the caller! */
Gaetan Rivet9e47fa42020-02-26 15:59:22 +01003324 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003325 eval_ret = tcpcheck_eval_connect(check, rule);
3326 must_read = 1; last_read = 0;
3327 break;
3328 case TCPCHK_ACT_SEND:
3329 check->current_step = rule;
3330 eval_ret = tcpcheck_eval_send(check, rule);
3331 must_read = 1;
3332 break;
3333 case TCPCHK_ACT_EXPECT:
3334 check->current_step = rule;
3335 if (must_read) {
3336 if (check->proxy->timeout.check)
3337 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
Baptiste Assmann22b09d22015-05-01 08:03:04 +02003338
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003339 /* If we already subscribed, then we tried to received and
3340 * failed, so there's no point trying again.
3341 */
3342 if (check->wait_list.events & SUB_RETRY_RECV)
3343 goto out;
3344 if (conn->mux->rcv_buf(cs, &check->bi, b_size(&check->bi), 0) <= 0) {
3345 if (conn->flags & (CO_FL_ERROR|CO_FL_SOCK_RD_SH) || cs->flags & CS_FL_ERROR) {
3346 last_read = 1;
3347 if ((conn->flags & CO_FL_ERROR || cs->flags & CS_FL_ERROR) && !b_data(&check->bi)) {
3348 /* Report network errors only if we got no other data. Otherwise
3349 * we'll let the upper layers decide whether the response is OK
3350 * or not. It is very common that an RST sent by the server is
3351 * reported as an error just after the last data chunk.
3352 */
3353 goto out_end_tcpcheck;
3354 }
3355 }
3356 else {
3357 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
3358 goto out;
3359 }
3360 }
Willy Tarreauf2c87352015-05-13 12:08:21 +02003361
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003362 /* Check that response body is not empty... */
3363 if (!b_data(&check->bi)) {
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003364 if (!last_read)
3365 goto out;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003366
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003367 /* empty response */
3368 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
3369 tcpcheck_get_step_id(check, rule));
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02003370 if (rule->comment)
3371 chunk_appendf(&trash, " comment: '%s'", rule->comment);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003372 set_server_check_status(check, rule->expect.err_status, trash.area);
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003373 ret = -1;
3374 goto out_end_tcpcheck;
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003375 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003376 must_read = 0;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003377 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003378
Christopher Faulete5870d82020-04-15 11:32:03 +02003379 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
3380 ? tcpcheck_eval_expect_http(check, rule, last_read)
3381 : tcpcheck_eval_expect(check, rule, last_read));
3382
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003383 if (eval_ret == TCPCHK_EVAL_WAIT) {
3384 check->current_step = rule->expect.head;
3385 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Gaetan Rivet9dcb09f2020-02-07 15:37:17 +01003386 }
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003387 break;
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003388 case TCPCHK_ACT_ACTION_KW:
3389 /* Don't update the current step */
3390 eval_ret = tcpcheck_eval_action_kw(check, rule);
3391 break;
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003392 default:
3393 /* Otherwise, just go to the next one and don't update
3394 * the current step
3395 */
3396 eval_ret = TCPCHK_EVAL_CONTINUE;
3397 break;
3398 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003399
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003400 switch (eval_ret) {
3401 case TCPCHK_EVAL_CONTINUE:
3402 break;
3403 case TCPCHK_EVAL_WAIT:
3404 goto out;
3405 case TCPCHK_EVAL_STOP:
3406 goto out_end_tcpcheck;
Baptiste Assmann248f1172018-03-01 21:49:01 +01003407 }
Baptiste Assmann248f1172018-03-01 21:49:01 +01003408 }
3409
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003410 /* All rules was evaluated */
Christopher Fauletdf38f882020-04-07 16:04:38 +02003411 if (check->current_step) {
3412 rule = check->current_step;
3413
3414 if (rule->action == TCPCHK_ACT_EXPECT) {
Christopher Faulete5870d82020-04-15 11:32:03 +02003415 struct buffer *msg;
3416
3417 if (check->server &&
3418 (check->server->proxy->options & PR_O_DISABLE404) &&
3419 (check->server->next_state != SRV_ST_STOPPED) &&
3420 (check->code == 404)) {
3421 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
3422 goto out_end_tcpcheck;
3423 }
Christopher Fauletdf38f882020-04-07 16:04:38 +02003424
Christopher Faulete5870d82020-04-15 11:32:03 +02003425 msg = alloc_trash_chunk();
Christopher Fauletdf38f882020-04-07 16:04:38 +02003426 if (msg)
3427 tcpcheck_onsuccess_message(msg, check, rule, ist(NULL));
3428 set_server_check_status(check, rule->expect.ok_status,
3429 (msg ? b_head(msg) : "(tcp-check)"));
3430 free_trash_chunk(msg);
3431 }
3432 else if (rule->action == TCPCHK_ACT_CONNECT) {
3433 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
3434 enum healthcheck_status status = ((conn && ssl_sock_is_ssl(conn)) ? HCHK_STATUS_L6OK : HCHK_STATUS_L4OK);
3435
3436 set_server_check_status(check, status, msg);
3437 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003438 }
Christopher Fauletec07e382020-04-07 14:56:26 +02003439 else
3440 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003441
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003442 out_end_tcpcheck:
Willy Tarreauef91c932019-07-23 14:37:47 +02003443 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003444 chk_report_conn_err(check, errno, 0);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003445
Baptiste Assmann69e273f2013-12-11 00:52:19 +01003446 /* cleanup before leaving */
Christopher Fauletb2c2e0f2020-03-30 11:05:10 +02003447 check->current_step = NULL;
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003448 if (check->sess != NULL) {
Gaetan Rivet13a50432020-02-21 18:13:44 +01003449 vars_prune(&check->vars, check->sess, NULL);
Gaetan Rivet05d692d2020-02-14 17:42:54 +01003450 session_free(check->sess);
3451 check->sess = NULL;
3452 }
3453 out:
3454 return retcode;
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02003455}
3456
Christopher Faulet31c30fd2020-03-26 21:10:03 +01003457static const char *init_check(struct check *check, int type)
Simon Hormanb1900d52015-01-30 11:22:54 +09003458{
3459 check->type = type;
3460
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003461 b_reset(&check->bi); check->bi.size = global.tune.chksize;
3462 b_reset(&check->bo); check->bo.size = global.tune.chksize;
Simon Hormanb1900d52015-01-30 11:22:54 +09003463
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003464 check->bi.area = calloc(check->bi.size, sizeof(char));
3465 check->bo.area = calloc(check->bo.size, sizeof(char));
3466
3467 if (!check->bi.area || !check->bo.area)
Simon Hormanb1900d52015-01-30 11:22:54 +09003468 return "out of memory while allocating check buffer";
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003469
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003470 check->wait_list.tasklet = tasklet_new();
3471 if (!check->wait_list.tasklet)
Ilya Shipitsind4259502020-04-08 01:07:56 +05003472 return "out of memory while allocating check tasklet";
Willy Tarreau4f6516d2018-12-19 13:59:17 +01003473 check->wait_list.events = 0;
Willy Tarreau3c39a7d2019-06-14 14:42:29 +02003474 check->wait_list.tasklet->process = event_srv_chk_io;
3475 check->wait_list.tasklet->context = check;
Simon Hormanb1900d52015-01-30 11:22:54 +09003476 return NULL;
3477}
3478
Simon Hormanbfb5d332015-01-30 11:22:55 +09003479void free_check(struct check *check)
3480{
Christopher Faulet8892e5d2020-03-26 19:48:20 +01003481 task_destroy(check->task);
3482 if (check->wait_list.tasklet)
3483 tasklet_free(check->wait_list.tasklet);
3484
Willy Tarreauc9fa0482018-07-10 17:43:27 +02003485 free(check->bi.area);
3486 free(check->bo.area);
Christopher Faulet23d86d12018-01-25 11:36:35 +01003487 if (check->cs) {
3488 free(check->cs->conn);
3489 check->cs->conn = NULL;
3490 cs_free(check->cs);
3491 check->cs = NULL;
3492 }
Simon Hormanbfb5d332015-01-30 11:22:55 +09003493}
3494
Christopher Faulete5870d82020-04-15 11:32:03 +02003495static void free_tcpcheck_fmt(struct list *fmt)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003496{
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003497 struct logformat_node *lf, *lfb;
3498
Christopher Faulete5870d82020-04-15 11:32:03 +02003499 list_for_each_entry_safe(lf, lfb, fmt, list) {
3500 LIST_DEL(&lf->list);
3501 release_sample_expr(lf->expr);
3502 free(lf->arg);
3503 free(lf);
3504 }
3505}
3506
3507static void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
3508{
3509 if (!hdr)
3510 return;
3511
3512 free_tcpcheck_fmt(&hdr->value);
3513 free(hdr->name.ptr);
3514 free(hdr);
3515}
3516
3517static void free_tcpcheck_http_hdrs(struct list *hdrs)
3518{
3519 struct tcpcheck_http_hdr *hdr, *bhdr;
3520
3521 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
3522 LIST_DEL(&hdr->list);
3523 free_tcpcheck_http_hdr(hdr);
3524 }
3525}
3526
3527static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
3528{
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003529 if (!rule)
3530 return;
3531
3532 free(rule->comment);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003533 switch (rule->action) {
3534 case TCPCHK_ACT_SEND:
3535 switch (rule->send.type) {
3536 case TCPCHK_SEND_STRING:
3537 case TCPCHK_SEND_BINARY:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003538 free(rule->send.data.ptr);
3539 break;
3540 case TCPCHK_SEND_STRING_LF:
3541 case TCPCHK_SEND_BINARY_LF:
Christopher Faulete5870d82020-04-15 11:32:03 +02003542 free_tcpcheck_fmt(&rule->send.fmt);
3543 break;
3544 case TCPCHK_SEND_HTTP:
3545 free(rule->send.http.meth.str.area);
3546 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3547 free(rule->send.http.uri.ptr);
3548 else
3549 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
3550 free(rule->send.http.vsn.ptr);
3551 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
3552 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3553 free(rule->send.http.body.ptr);
3554 else
3555 free_tcpcheck_fmt(&rule->send.http.body_fmt);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003556 break;
3557 case TCPCHK_SEND_UNDEF:
3558 break;
3559 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003560 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003561 case TCPCHK_ACT_EXPECT:
Christopher Faulete5870d82020-04-15 11:32:03 +02003562 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
3563 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02003564 release_sample_expr(rule->expect.status_expr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003565 switch (rule->expect.type) {
3566 case TCPCHK_EXPECT_STRING:
3567 case TCPCHK_EXPECT_BINARY:
Christopher Faulete5870d82020-04-15 11:32:03 +02003568 case TCPCHK_EXPECT_HTTP_STATUS:
3569 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003570 free(rule->expect.data.ptr);
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003571 break;
3572 case TCPCHK_EXPECT_REGEX:
3573 case TCPCHK_EXPECT_REGEX_BINARY:
Christopher Faulete5870d82020-04-15 11:32:03 +02003574 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
3575 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003576 regex_free(rule->expect.regex);
3577 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02003578 case TCPCHK_EXPECT_CUSTOM:
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003579 case TCPCHK_EXPECT_UNDEF:
3580 break;
3581 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003582 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003583 case TCPCHK_ACT_CONNECT:
Christopher Faulet79b31d42020-03-30 13:00:05 +02003584 free(rule->connect.sni);
Christopher Faulet98572322020-03-30 13:16:44 +02003585 free(rule->connect.alpn);
Christopher Fauletb7d30092020-03-30 15:19:03 +02003586 release_sample_expr(rule->connect.port_expr);
Christopher Faulet79b31d42020-03-30 13:00:05 +02003587 break;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003588 case TCPCHK_ACT_COMMENT:
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003589 break;
Gaetan Rivet707b52f2020-02-21 18:14:59 +01003590 case TCPCHK_ACT_ACTION_KW:
3591 free(rule->action_kw.rule);
3592 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003593 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003594
Christopher Fauletfd6c2292020-03-25 18:20:15 +01003595 if (in_pool)
3596 pool_free(pool_head_tcpcheck_rule, rule);
3597 else
3598 free(rule);
3599}
3600
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003601
Christopher Fauletce355072020-04-02 11:44:39 +02003602static struct tcpcheck_var *tcpcheck_var_create(const char *name)
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003603{
3604 struct tcpcheck_var *var = NULL;
3605
3606 var = calloc(1, sizeof(*var));
3607 if (var == NULL)
3608 return NULL;
3609
3610 var->name = ist2(strdup(name), strlen(name));
3611 if (var->name.ptr == NULL) {
3612 free(var);
3613 return NULL;
3614 }
3615
3616 LIST_INIT(&var->list);
3617 return var;
3618}
3619
3620static void tcpcheck_var_release(struct tcpcheck_var *var)
3621{
3622 if (!var)
3623 return;
3624
3625 free(var->name.ptr);
3626 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
3627 free(var->data.u.str.area);
3628 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
3629 free(var->data.u.meth.str.area);
3630 free(var);
3631}
3632
3633int dup_tcpcheck_vars(struct list *dst, struct list *src)
3634{
3635 struct tcpcheck_var *var, *new = NULL;
3636
3637 list_for_each_entry(var, src, list) {
3638 new = tcpcheck_var_create(var->name.ptr);
3639 if (!new)
3640 goto error;
3641 new->data.type = var->data.type;
3642 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
3643 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3644 goto error;
3645 if (var->data.type == SMP_T_STR)
3646 new->data.u.str.area[new->data.u.str.data] = 0;
3647 }
3648 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
3649 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
3650 goto error;
3651 new->data.u.str.area[new->data.u.str.data] = 0;
3652 new->data.u.meth.meth = var->data.u.meth.meth;
3653 }
3654 else
3655 new->data.u = var->data.u;
3656 LIST_ADDQ(dst, &new->list);
3657 }
3658 return 1;
3659
3660 error:
3661 free(new);
3662 return 0;
3663}
3664
3665static void free_tcpcheck_vars(struct list *vars)
3666{
3667 struct tcpcheck_var *var, *back;
3668
3669 list_for_each_entry_safe(var, back, vars, list) {
3670 LIST_DEL(&var->list);
3671 tcpcheck_var_release(var);
3672 }
3673}
3674
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003675void email_alert_free(struct email_alert *alert)
3676{
3677 struct tcpcheck_rule *rule, *back;
3678
3679 if (!alert)
3680 return;
3681
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003682 if (alert->rules.list) {
3683 list_for_each_entry_safe(rule, back, alert->rules.list, list) {
3684 LIST_DEL(&rule->list);
3685 free_tcpcheck(rule, 1);
3686 }
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003687 free_tcpcheck_vars(&alert->rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003688 free(alert->rules.list);
3689 alert->rules.list = NULL;
Christopher Fauletde1a75b2017-10-23 15:38:19 +02003690 }
Willy Tarreaubafbe012017-11-24 17:34:44 +01003691 pool_free(pool_head_email_alert, alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003692}
3693
Olivier Houchard9f6af332018-05-25 14:04:04 +02003694static struct task *process_email_alert(struct task *t, void *context, unsigned short state)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003695{
Olivier Houchard9f6af332018-05-25 14:04:04 +02003696 struct check *check = context;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003697 struct email_alertq *q;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003698 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003699
3700 q = container_of(check, typeof(*q), check);
3701
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003702 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003703 while (1) {
3704 if (!(check->state & CHK_ST_ENABLED)) {
3705 if (LIST_ISEMPTY(&q->email_alerts)) {
3706 /* All alerts processed, queue the task */
3707 t->expire = TICK_ETERNITY;
3708 task_queue(t);
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003709 goto end;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003710 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003711
3712 alert = LIST_NEXT(&q->email_alerts, typeof(alert), list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003713 LIST_DEL(&alert->list);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003714 t->expire = now_ms;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003715 check->tcpcheck_rules = &alert->rules;
Olivier Houchard0923fa42019-01-11 18:43:04 +01003716 check->status = HCHK_STATUS_INI;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003717 check->state |= CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003718 }
3719
Olivier Houchard9f6af332018-05-25 14:04:04 +02003720 process_chk(t, context, state);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003721 if (check->state & CHK_ST_INPROGRESS)
3722 break;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003723
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003724 alert = container_of(check->tcpcheck_rules, typeof(*alert), rules);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003725 email_alert_free(alert);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003726 check->tcpcheck_rules = NULL;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003727 check->server = NULL;
3728 check->state &= ~CHK_ST_ENABLED;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003729 }
Christopher Fauletc2a89a62017-10-23 15:54:24 +02003730 end:
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003731 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003732 return t;
3733}
3734
Christopher Faulet0108bb32017-10-20 21:34:32 +02003735/* Initializes mailer alerts for the proxy <p> using <mls> parameters.
3736 *
3737 * The function returns 1 in success case, otherwise, it returns 0 and err is
3738 * filled.
3739 */
3740int init_email_alert(struct mailers *mls, struct proxy *p, char **err)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003741{
Christopher Faulet0108bb32017-10-20 21:34:32 +02003742 struct mailer *mailer;
3743 struct email_alertq *queues;
3744 const char *err_str;
3745 int i = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003746
Christopher Faulet0108bb32017-10-20 21:34:32 +02003747 if ((queues = calloc(mls->count, sizeof(*queues))) == NULL) {
3748 memprintf(err, "out of memory while allocating mailer alerts queues");
mildis5ab01cb2018-10-02 16:46:34 +02003749 goto fail_no_queue;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003750 }
3751
Christopher Faulet0108bb32017-10-20 21:34:32 +02003752 for (mailer = mls->mailer_list; mailer; i++, mailer = mailer->next) {
3753 struct email_alertq *q = &queues[i];
3754 struct check *check = &q->check;
3755 struct task *t;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003756
3757 LIST_INIT(&q->email_alerts);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003758 HA_SPIN_INIT(&q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003759 check->inter = mls->timeout.mail;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003760 check->rise = DEF_AGENT_RISETIME;
Olivier Houchardc98aa1f2019-01-11 18:17:17 +01003761 check->proxy = p;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003762 check->fall = DEF_AGENT_FALLTIME;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003763 if ((err_str = init_check(check, PR_O2_TCPCHK_CHK))) {
3764 memprintf(err, "%s", err_str);
3765 goto error;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003766 }
3767
3768 check->xprt = mailer->xprt;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003769 check->addr = mailer->addr;
Christopher Fauletb797ae12018-03-27 15:35:35 +02003770 check->port = get_host_port(&mailer->addr);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003771
Emeric Brunc60def82017-09-27 14:59:38 +02003772 if ((t = task_new(MAX_THREADS_MASK)) == NULL) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02003773 memprintf(err, "out of memory while allocating mailer alerts task");
3774 goto error;
3775 }
3776
3777 check->task = t;
3778 t->process = process_email_alert;
3779 t->context = check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003780
Christopher Faulet0108bb32017-10-20 21:34:32 +02003781 /* check this in one ms */
3782 t->expire = TICK_ETERNITY;
3783 check->start = now;
3784 task_queue(t);
3785 }
3786
3787 mls->users++;
3788 free(p->email_alert.mailers.name);
3789 p->email_alert.mailers.m = mls;
3790 p->email_alert.queues = queues;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003791 return 0;
Christopher Faulet0108bb32017-10-20 21:34:32 +02003792
3793 error:
3794 for (i = 0; i < mls->count; i++) {
3795 struct email_alertq *q = &queues[i];
3796 struct check *check = &q->check;
3797
Christopher Faulet0108bb32017-10-20 21:34:32 +02003798 free_check(check);
3799 }
3800 free(queues);
mildis5ab01cb2018-10-02 16:46:34 +02003801 fail_no_queue:
Christopher Faulet0108bb32017-10-20 21:34:32 +02003802 return 1;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003803}
3804
3805
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003806static int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003807{
Gaetan Rivet4038b942020-02-26 16:19:40 +01003808 struct tcpcheck_rule *tcpcheck, *prev_check;
Gaetan Rivetb616add2020-02-07 15:37:17 +01003809 struct tcpcheck_expect *expect;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003810
Willy Tarreaubafbe012017-11-24 17:34:44 +01003811 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003812 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003813 memset(tcpcheck, 0, sizeof(*tcpcheck));
Gaetan Rivetb616add2020-02-07 15:37:17 +01003814 tcpcheck->action = TCPCHK_ACT_EXPECT;
3815
3816 expect = &tcpcheck->expect;
3817 expect->type = TCPCHK_EXPECT_STRING;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02003818 LIST_INIT(&expect->onerror_fmt);
3819 LIST_INIT(&expect->onsuccess_fmt);
Christopher Fauletec07e382020-04-07 14:56:26 +02003820 expect->ok_status = HCHK_STATUS_L7OKD;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02003821 expect->err_status = HCHK_STATUS_L7RSP;
3822 expect->tout_status = HCHK_STATUS_L7TOUT;
Christopher Fauletf930e4c2020-04-10 09:20:02 +02003823 expect->data = ist2(strdup(str), strlen(str));
3824 if (!expect->data.ptr) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003825 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003826 return 0;
3827 }
3828
Gaetan Rivet4038b942020-02-26 16:19:40 +01003829 /* All tcp-check expect points back to the first inverse expect rule
3830 * in a chain of one or more expect rule, potentially itself.
3831 */
Gaetan Rivetb616add2020-02-07 15:37:17 +01003832 tcpcheck->expect.head = tcpcheck;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003833 list_for_each_entry_rev(prev_check, rules->list, list) {
Gaetan Rivet4038b942020-02-26 16:19:40 +01003834 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet12d57402020-04-10 09:58:42 +02003835 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
Gaetan Rivetb616add2020-02-07 15:37:17 +01003836 tcpcheck->expect.head = prev_check;
Gaetan Rivet4038b942020-02-26 16:19:40 +01003837 continue;
3838 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01003839 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Gaetan Rivet4038b942020-02-26 16:19:40 +01003840 break;
3841 }
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003842 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003843 return 1;
3844}
3845
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003846static int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003847{
3848 struct tcpcheck_rule *tcpcheck;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003849 struct tcpcheck_send *send;
Willy Tarreau64345aa2016-08-10 19:29:09 +02003850 const char *in;
3851 char *dst;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003852 int i;
3853
Willy Tarreaubafbe012017-11-24 17:34:44 +01003854 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003855 return 0;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003856 memset(tcpcheck, 0, sizeof(*tcpcheck));
3857 tcpcheck->action = TCPCHK_ACT_SEND;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003858
3859 send = &tcpcheck->send;
3860 send->type = TCPCHK_SEND_STRING;
3861
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003862 for (i = 0; strs[i]; i++)
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003863 send->data.len += strlen(strs[i]);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003864
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003865 send->data.ptr = malloc(send->data.len + 1);
3866 if (!isttest(send->data)) {
Willy Tarreaubafbe012017-11-24 17:34:44 +01003867 pool_free(pool_head_tcpcheck_rule, tcpcheck);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003868 return 0;
3869 }
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003870
Christopher Fauletf50f4e92020-03-30 19:52:29 +02003871 dst = send->data.ptr;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003872 for (i = 0; strs[i]; i++)
Willy Tarreau64345aa2016-08-10 19:29:09 +02003873 for (in = strs[i]; (*dst = *in++); dst++);
3874 *dst = 0;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003875
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003876 LIST_ADDQ(rules->list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003877 return 1;
3878}
3879
Christopher Faulet0108bb32017-10-20 21:34:32 +02003880static int enqueue_one_email_alert(struct proxy *p, struct server *s,
3881 struct email_alertq *q, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003882{
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003883 struct email_alert *alert;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003884 struct tcpcheck_rule *tcpcheck;
3885 struct check *check = &q->check;
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003886
Willy Tarreaubafbe012017-11-24 17:34:44 +01003887 if ((alert = pool_alloc(pool_head_email_alert)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003888 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003889 LIST_INIT(&alert->list);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003890 alert->rules.flags = 0;
3891 alert->rules.list = calloc(1, sizeof(*alert->rules.list));
3892 if (!alert->rules.list)
3893 goto error;
3894 LIST_INIT(alert->rules.list);
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02003895 LIST_INIT(&alert->rules.preset_vars); /* unused for email alerts */
Christopher Faulet0108bb32017-10-20 21:34:32 +02003896 alert->srv = s;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003897
Willy Tarreaubafbe012017-11-24 17:34:44 +01003898 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003899 goto error;
Christopher Faulet31dff9b2017-10-23 15:45:20 +02003900 memset(tcpcheck, 0, sizeof(*tcpcheck));
3901 tcpcheck->action = TCPCHK_ACT_CONNECT;
3902 tcpcheck->comment = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01003903
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003904 LIST_ADDQ(alert->rules.list, &tcpcheck->list);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003905
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003906 if (!add_tcpcheck_expect_str(&alert->rules, "220 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003907 goto error;
3908
3909 {
3910 const char * const strs[4] = { "EHLO ", p->email_alert.myhostname, "\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003911 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003912 goto error;
3913 }
3914
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003915 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003916 goto error;
3917
3918 {
3919 const char * const strs[4] = { "MAIL FROM:<", p->email_alert.from, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003920 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003921 goto error;
3922 }
3923
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003924 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003925 goto error;
3926
3927 {
3928 const char * const strs[4] = { "RCPT TO:<", p->email_alert.to, ">\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003929 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003930 goto error;
3931 }
3932
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003933 if (!add_tcpcheck_expect_str(&alert->rules, "250 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003934 goto error;
3935
3936 {
3937 const char * const strs[2] = { "DATA\r\n" };
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003938 if (!add_tcpcheck_send_strs(&alert->rules, strs))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003939 goto error;
3940 }
3941
Christopher Faulet5d503fc2020-03-30 20:34:34 +02003942 if (!add_tcpcheck_expect_str(&alert->rules, "354 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003943 goto error;
3944
3945 {
3946 struct tm tm;
3947 char datestr[48];
3948 const char * const strs[18] = {
Pieter Baauw5e0964e2016-02-13 16:27:35 +01003949 "From: ", p->email_alert.from, "\r\n",
3950 "To: ", p->email_alert.to, "\r\n",
3951 "Date: ", datestr, "\r\n",
3952 "Subject: [HAproxy Alert] ", msg, "\r\n",
3953 "\r\n",
3954 msg, "\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003955 "\r\n",
Pieter Baauwed35c372015-07-22 19:51:54 +02003956 ".\r\n",
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003957 NULL
3958 };
3959
3960 get_localtime(date.tv_sec, &tm);
3961
3962 if (strftime(datestr, sizeof(datestr), "%a, %d %b %Y %T %z (%Z)", &tm) == 0) {
3963 goto error;
3964 }
3965
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[2] = { "QUIT\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, "221 "))
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003980 goto error;
3981
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003982 HA_SPIN_LOCK(EMAIL_ALERTS_LOCK, &q->lock);
Christopher Faulet0108bb32017-10-20 21:34:32 +02003983 task_wakeup(check->task, TASK_WOKEN_MSG);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003984 LIST_ADDQ(&q->email_alerts, &alert->list);
Christopher Faulet2a944ee2017-11-07 10:42:54 +01003985 HA_SPIN_UNLOCK(EMAIL_ALERTS_LOCK, &q->lock);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003986 return 1;
3987
3988error:
3989 email_alert_free(alert);
3990 return 0;
3991}
3992
Christopher Faulet0108bb32017-10-20 21:34:32 +02003993static void enqueue_email_alert(struct proxy *p, struct server *s, const char *msg)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09003994{
3995 int i;
3996 struct mailer *mailer;
3997
3998 for (i = 0, mailer = p->email_alert.mailers.m->mailer_list;
3999 i < p->email_alert.mailers.m->count; i++, mailer = mailer->next) {
Christopher Faulet0108bb32017-10-20 21:34:32 +02004000 if (!enqueue_one_email_alert(p, s, &p->email_alert.queues[i], msg)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01004001 ha_alert("Email alert [%s] could not be enqueued: out of memory\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004002 return;
4003 }
4004 }
4005
4006 return;
4007}
4008
4009/*
4010 * Send email alert if configured.
4011 */
Simon Horman64e34162015-02-06 11:11:57 +09004012void send_email_alert(struct server *s, int level, const char *format, ...)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004013{
4014 va_list argp;
4015 char buf[1024];
4016 int len;
4017 struct proxy *p = s->proxy;
4018
Christopher Faulet0108bb32017-10-20 21:34:32 +02004019 if (!p->email_alert.mailers.m || level > p->email_alert.level || format == NULL)
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004020 return;
4021
4022 va_start(argp, format);
4023 len = vsnprintf(buf, sizeof(buf), format, argp);
4024 va_end(argp);
4025
Thierry FOURNIER62c8a212017-02-09 12:19:27 +01004026 if (len < 0 || len >= sizeof(buf)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +01004027 ha_alert("Email alert [%s] could not format message\n", p->id);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004028 return;
4029 }
4030
Christopher Faulet0108bb32017-10-20 21:34:32 +02004031 enqueue_email_alert(p, s, buf);
Simon Horman0ba0e4a2015-01-30 11:23:00 +09004032}
4033
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004034/*
4035 * Return value:
4036 * the port to be used for the health check
4037 * 0 in case no port could be found for the check
4038 */
Christopher Faulet31c30fd2020-03-26 21:10:03 +01004039static int srv_check_healthcheck_port(struct check *chk)
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004040{
4041 int i = 0;
4042 struct server *srv = NULL;
4043
4044 srv = chk->server;
4045
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004046 /* by default, we use the health check port ocnfigured */
4047 if (chk->port > 0)
4048 return chk->port;
4049
4050 /* try to get the port from check_core.addr if check.port not set */
4051 i = get_host_port(&chk->addr);
4052 if (i > 0)
4053 return i;
4054
4055 /* try to get the port from server address */
4056 /* prevent MAPPORTS from working at this point, since checks could
4057 * not be performed in such case (MAPPORTS impose a relative ports
4058 * based on live traffic)
4059 */
4060 if (srv->flags & SRV_F_MAPPORTS)
4061 return 0;
Willy Tarreau04276f32017-01-06 17:41:29 +01004062
4063 i = srv->svc_port; /* by default */
Baptiste Assmann95db2bc2016-06-13 14:15:41 +02004064 if (i > 0)
4065 return i;
4066
4067 return 0;
4068}
4069
Willy Tarreau172f5ce2018-11-26 11:21:50 +01004070REGISTER_POST_CHECK(start_checks);
Baptiste Assmann5ecb77f2013-10-06 23:24:13 +02004071
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004072static int check_proxy_tcpcheck(struct proxy *px)
4073{
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004074 struct tcpcheck_rule *chk, *back;
Christopher Faulete5870d82020-04-15 11:32:03 +02004075 char *comment = NULL, *errmsg = NULL;
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004076 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004077 int ret = 0;
4078
Christopher Faulete5870d82020-04-15 11:32:03 +02004079 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
4080 deinit_proxy_tcpcheck(px);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004081 goto out;
Christopher Faulete5870d82020-04-15 11:32:03 +02004082 }
4083
4084 free(px->check_command);
4085 free(px->check_path);
4086 px->check_command = px->check_path = NULL;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004087
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004088 if (!px->tcpcheck_rules.list) {
Christopher Faulet404f9192020-04-09 23:13:54 +02004089 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
4090 ret |= ERR_ALERT | ERR_FATAL;
4091 goto out;
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004092 }
4093
Christopher Faulete5870d82020-04-15 11:32:03 +02004094 /* HTTP ruleset */
4095 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4096 struct tcpcheck_rule *next;
4097
4098 /* move remaining send rule from "option httpchk" line to the right place */
4099 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
4100 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
4101 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
4102 if (next && next->action == TCPCHK_ACT_CONNECT) {
4103 LIST_DEL(&chk->list);
4104 LIST_ADD(&next->list, &chk->list);
4105 chk->index = next->index;
4106 }
4107 }
4108
4109 /* add implicit expect rule if the last one is a send. */
4110 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
4111 if (chk && chk->action == TCPCHK_ACT_SEND) {
4112 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "rstatus", "^[23]", ""},
4113 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
4114 px->conf.file, px->conf.line, &errmsg);
4115 if (!next) {
4116 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
4117 "(%s).\n", px->id, errmsg);
4118 free(errmsg);
4119 ret |= ERR_ALERT | ERR_FATAL;
4120 goto out;
4121 }
4122 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
4123 next->index = chk->index;
4124 }
4125 }
4126
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004127 /* If there is no connect rule preceeding all send / expect rules, an
4128 * implicit one is inserted before all others
4129 */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004130 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004131 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4132 chk = calloc(1, sizeof(*chk));
4133 if (!chk) {
4134 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
4135 "(out of memory).\n", px->id);
4136 ret |= ERR_ALERT | ERR_FATAL;
4137 goto out;
4138 }
4139 chk->action = TCPCHK_ACT_CONNECT;
Christopher Fauletbb591a12020-04-01 16:52:17 +02004140 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004141 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004142 }
Christopher Faulet6f2a5e42020-04-01 13:11:41 +02004143
4144 /* Now remove comment rules */
4145 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
4146 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT) {
4147 free(comment);
4148 comment = NULL;
4149 }
4150
4151 prev_action = chk->action;
4152 switch (chk->action) {
4153 case TCPCHK_ACT_COMMENT:
4154 free(comment);
4155 comment = chk->comment;
4156 LIST_DEL(&chk->list);
4157 free(chk);
4158 break;
4159 case TCPCHK_ACT_CONNECT:
4160 if (!chk->comment && comment)
4161 chk->comment = strdup(comment);
4162 /* fall though */
4163 case TCPCHK_ACT_ACTION_KW:
4164 free(comment);
4165 comment = NULL;
4166 break;
4167 case TCPCHK_ACT_SEND:
4168 case TCPCHK_ACT_EXPECT:
4169 if (!chk->comment && comment)
4170 chk->comment = strdup(comment);
4171 break;
4172 }
4173 }
4174 free(comment);
4175 comment = NULL;
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004176
4177 out:
4178 return ret;
4179}
4180
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004181static int init_srv_check(struct server *srv)
4182{
4183 const char *err;
4184 struct tcpcheck_rule *r;
4185 int ret = 0;
4186
4187 if (!srv->do_check)
4188 goto out;
4189
4190
4191 /* If neither a port nor an addr was specified and no check transport
4192 * layer is forced, then the transport layer used by the checks is the
4193 * same as for the production traffic. Otherwise we use raw_sock by
4194 * default, unless one is specified.
4195 */
4196 if (!srv->check.port && !is_addr(&srv->check.addr)) {
4197 if (!srv->check.use_ssl && srv->use_ssl != -1) {
4198 srv->check.use_ssl = srv->use_ssl;
4199 srv->check.xprt = srv->xprt;
4200 }
4201 else if (srv->check.use_ssl == 1)
4202 srv->check.xprt = xprt_get(XPRT_SSL);
4203
4204 srv->check.send_proxy |= (srv->pp_opts);
4205 }
4206
4207 /* validate <srv> server health-check settings */
4208
4209 /* We need at least a service port, a check port or the first tcp-check
4210 * rule must be a 'connect' one when checking an IPv4/IPv6 server.
4211 */
4212 if ((srv_check_healthcheck_port(&srv->check) != 0) ||
4213 (!is_inet_addr(&srv->check.addr) && (is_addr(&srv->check.addr) || !is_inet_addr(&srv->addr))))
4214 goto init;
4215
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004216 if (!srv->proxy->tcpcheck_rules.list || LIST_ISEMPTY(srv->proxy->tcpcheck_rules.list)) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004217 ha_alert("config: %s '%s': server '%s' has neither service port nor check port.\n",
4218 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4219 ret |= ERR_ALERT | ERR_ABORT;
4220 goto out;
4221 }
4222
4223 /* search the first action (connect / send / expect) in the list */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004224 r = get_first_tcpcheck_rule(&srv->proxy->tcpcheck_rules);
Christopher Faulet5c288742020-03-31 08:15:58 +02004225 if (!r || (r->action != TCPCHK_ACT_CONNECT) || (!r->connect.port && !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004226 ha_alert("config: %s '%s': server '%s' has neither service port nor check port "
4227 "nor tcp_check rule 'connect' with port information.\n",
4228 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4229 ret |= ERR_ALERT | ERR_ABORT;
4230 goto out;
4231 }
4232
4233 /* scan the tcp-check ruleset to ensure a port has been configured */
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004234 list_for_each_entry(r, srv->proxy->tcpcheck_rules.list, list) {
Christopher Faulet5c288742020-03-31 08:15:58 +02004235 if ((r->action == TCPCHK_ACT_CONNECT) && (!r->connect.port || !get_host_port(&r->connect.addr))) {
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004236 ha_alert("config: %s '%s': server '%s' has neither service port nor check port, "
4237 "and a tcp_check rule 'connect' with no port information.\n",
4238 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4239 ret |= ERR_ALERT | ERR_ABORT;
4240 goto out;
4241 }
4242 }
4243
4244 init:
4245 err = init_check(&srv->check, srv->proxy->options2 & PR_O2_CHK_ANY);
4246 if (err) {
4247 ha_alert("config: %s '%s': unable to init check for server '%s' (%s).\n",
4248 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4249 ret |= ERR_ALERT | ERR_ABORT;
4250 goto out;
4251 }
4252 srv->check.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED;
4253 global.maxsock++;
4254
4255 out:
4256 return ret;
4257}
4258
4259static int init_srv_agent_check(struct server *srv)
4260{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004261 struct tcpcheck_rule *chk;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004262 const char *err;
4263 int ret = 0;
4264
4265 if (!srv->do_agent)
4266 goto out;
4267
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004268 /* If there is no connect rule preceeding all send / expect rules, an
4269 * implicit one is inserted before all others.
4270 */
4271 chk = get_first_tcpcheck_rule(srv->agent.tcpcheck_rules);
4272 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
4273 chk = calloc(1, sizeof(*chk));
4274 if (!chk) {
4275 ha_alert("config : %s '%s': unable to add implicit tcp-check connect rule"
4276 " to agent-check for server '%s' (out of memory).\n",
4277 proxy_type_str(srv->proxy), srv->proxy->id, srv->id);
4278 ret |= ERR_ALERT | ERR_FATAL;
4279 goto out;
4280 }
4281 chk->action = TCPCHK_ACT_CONNECT;
4282 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
4283 LIST_ADD(srv->agent.tcpcheck_rules->list, &chk->list);
4284 }
4285
4286
4287 err = init_check(&srv->agent, PR_O2_TCPCHK_CHK);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004288 if (err) {
4289 ha_alert("config: %s '%s': unable to init agent-check for server '%s' (%s).\n",
4290 proxy_type_str(srv->proxy), srv->proxy->id, srv->id, err);
4291 ret |= ERR_ALERT | ERR_ABORT;
4292 goto out;
4293 }
4294
4295 if (!srv->agent.inter)
4296 srv->agent.inter = srv->check.inter;
4297
4298 srv->agent.state |= CHK_ST_CONFIGURED | CHK_ST_ENABLED | CHK_ST_AGENT;
4299 global.maxsock++;
4300
4301 out:
4302 return ret;
4303}
4304
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004305void deinit_proxy_tcpcheck(struct proxy *px)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004306{
Christopher Faulet7a1e2e12020-04-02 18:05:11 +02004307 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004308 px->tcpcheck_rules.flags = 0;
4309 px->tcpcheck_rules.list = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004310}
4311
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004312static void deinit_srv_check(struct server *srv)
4313{
Christopher Fauletce8111e2020-04-06 15:04:11 +02004314 if (srv->check.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004315 free_check(&srv->check);
Christopher Fauletce8111e2020-04-06 15:04:11 +02004316 srv->check.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED;
4317 srv->do_check = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004318}
4319
4320
4321static void deinit_srv_agent_check(struct server *srv)
4322{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004323 if (srv->agent.tcpcheck_rules) {
4324 free_tcpcheck_vars(&srv->agent.tcpcheck_rules->preset_vars);
4325 free(srv->agent.tcpcheck_rules);
4326 srv->agent.tcpcheck_rules = NULL;
4327 }
4328
4329 if (srv->agent.state & CHK_ST_CONFIGURED)
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004330 free_check(&srv->agent);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02004331
4332 srv->agent.state &= ~CHK_ST_CONFIGURED & ~CHK_ST_ENABLED & ~CHK_ST_AGENT;
4333 srv->do_agent = 0;
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004334}
4335
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004336static void deinit_tcpchecks()
4337{
4338 struct tcpcheck_ruleset *rs, *rsb;
4339 struct tcpcheck_rule *r, *rb;
4340
4341 list_for_each_entry_safe(rs, rsb, &tcpchecks_list, list) {
4342 LIST_DEL(&rs->list);
4343 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4344 LIST_DEL(&r->list);
4345 free_tcpcheck(r, 0);
4346 }
4347 free(rs->name);
4348 free(rs);
4349 }
4350}
4351
Christopher Fauleta202d1d2020-03-26 17:38:49 +01004352
4353REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004354REGISTER_POST_SERVER_CHECK(init_srv_check);
4355REGISTER_POST_SERVER_CHECK(init_srv_agent_check);
4356
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004357REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004358REGISTER_SERVER_DEINIT(deinit_srv_check);
4359REGISTER_SERVER_DEINIT(deinit_srv_agent_check);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004360REGISTER_POST_DEINIT(deinit_tcpchecks);
Christopher Faulet8892e5d2020-03-26 19:48:20 +01004361
Christopher Faulet404f9192020-04-09 23:13:54 +02004362static struct tcpcheck_ruleset *tcpcheck_ruleset_lookup(const char *name)
4363{
4364 struct tcpcheck_ruleset *rs;
4365
4366 list_for_each_entry(rs, &tcpchecks_list, list) {
4367 if (strcmp(rs->name, name) == 0)
4368 return rs;
4369 }
4370 return NULL;
4371}
4372
4373static struct tcpcheck_ruleset *tcpcheck_ruleset_create(const char *name)
4374{
4375 struct tcpcheck_ruleset *rs;
4376
4377 rs = calloc(1, sizeof(*rs));
4378 if (rs == NULL)
4379 return NULL;
4380
4381 rs->name = strdup(name);
4382 if (rs->name == NULL) {
4383 free(rs);
4384 return NULL;
4385 }
4386
4387 LIST_INIT(&rs->list);
4388 LIST_INIT(&rs->rules);
4389 LIST_ADDQ(&tcpchecks_list, &rs->list);
4390 return rs;
4391}
4392
4393static void tcpcheck_ruleset_release(struct tcpcheck_ruleset *rs)
4394{
4395 struct tcpcheck_rule *r, *rb;
4396 if (!rs)
4397 return;
4398
4399 LIST_DEL(&rs->list);
4400 list_for_each_entry_safe(r, rb, &rs->rules, list) {
4401 LIST_DEL(&r->list);
4402 free_tcpcheck(r, 0);
4403 }
4404 free(rs->name);
4405 free(rs);
4406}
4407
Christopher Fauletba3c68f2020-04-01 16:27:05 +02004408/* extracts check payload at a fixed position and length */
4409static int
4410smp_fetch_chk_payload(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
4411{
4412 unsigned int buf_offset = ((arg_p[0].type == ARGT_SINT) ? arg_p[0].data.sint : 0);
4413 unsigned int buf_size = ((arg_p[1].type == ARGT_SINT) ? arg_p[1].data.sint : 0);
4414 struct server *srv = (smp->sess ? objt_server(smp->sess->origin) : NULL);
4415 struct buffer *buf;
4416
4417 if (!srv || !srv->do_check)
4418 return 0;
4419
4420 buf = &srv->check.bi;
4421 if (buf_offset > b_data(buf))
4422 goto no_match;
4423 if (buf_offset + buf_size > b_data(buf))
4424 buf_size = 0;
4425
4426 /* init chunk as read only */
4427 smp->data.type = SMP_T_STR;
4428 smp->flags = SMP_F_VOLATILE | SMP_F_CONST;
4429 chunk_initlen(&smp->data.u.str, b_head(buf) + buf_offset, 0, (buf_size ? buf_size : (b_data(buf) - buf_offset)));
4430
4431 return 1;
4432
4433 no_match:
4434 smp->flags = 0;
4435 return 0;
4436}
4437
4438static struct sample_fetch_kw_list smp_kws = {ILH, {
4439 { "check.payload", smp_fetch_chk_payload, ARG2(0,SINT,SINT), NULL, SMP_T_STR, SMP_USE_INTRN },
4440 { /* END */ },
4441}};
4442
4443INITCALL1(STG_REGISTER, sample_register_fetches, &smp_kws);
4444
4445
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004446struct action_kw_list tcp_check_keywords = {
4447 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
4448};
4449
4450/* Return the struct action_kw associated to a keyword */
4451static struct action_kw *action_kw_tcp_check_lookup(const char *kw)
4452{
4453 return action_lookup(&tcp_check_keywords.list, kw);
4454}
4455
4456static void action_kw_tcp_check_build_list(struct buffer *chk)
4457{
4458 action_build_list(&tcp_check_keywords.list, chk);
4459}
4460
4461/* Create a tcp-check rule resulting from parsing a custom keyword. */
4462static struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004463 struct list *rules, struct action_kw *kw,
4464 const char *file, int line, char **errmsg)
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004465{
4466 struct tcpcheck_rule *chk = NULL;
4467 struct act_rule *actrule = NULL;
4468
4469 actrule = calloc(1, sizeof(*actrule));
4470 if (!actrule) {
4471 memprintf(errmsg, "out of memory");
4472 goto error;
4473 }
4474 actrule->kw = kw;
4475 actrule->from = ACT_F_TCP_CHK;
4476
4477 cur_arg++;
4478 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
4479 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
4480 goto error;
4481 }
4482
4483 chk = calloc(1, sizeof(*chk));
4484 if (!chk) {
4485 memprintf(errmsg, "out of memory");
4486 goto error;
4487 }
4488 chk->action = TCPCHK_ACT_ACTION_KW;
4489 chk->action_kw.rule = actrule;
4490 return chk;
4491
4492 error:
4493 free(actrule);
4494 return NULL;
4495}
4496
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004497static struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Fauletb7d30092020-03-30 15:19:03 +02004498 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004499{
4500 struct tcpcheck_rule *chk = NULL;
Christopher Faulet5c288742020-03-31 08:15:58 +02004501 struct sockaddr_storage *sk = NULL;
Christopher Faulet98572322020-03-30 13:16:44 +02004502 char *comment = NULL, *sni = NULL, *alpn = NULL;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004503 struct sample_expr *port_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004504 unsigned short conn_opts = 0;
4505 long port = 0;
Christopher Faulet98572322020-03-30 13:16:44 +02004506 int alpn_len = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004507
4508 list_for_each_entry(chk, rules, list) {
Christopher Faulete5870d82020-04-15 11:32:03 +02004509 if (chk->action == TCPCHK_ACT_CONNECT)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004510 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02004511 if (chk->action == TCPCHK_ACT_COMMENT ||
4512 chk->action == TCPCHK_ACT_ACTION_KW ||
4513 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
4514 continue;
4515
Gaetan Rivet707b52f2020-02-21 18:14:59 +01004516 memprintf(errmsg, "first step MUST also be a 'connect', "
4517 "optionnaly preceded by a 'set-var', an 'unset-var' or a 'comment', "
4518 "when there is a 'connect' step in the tcp-check ruleset");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004519 goto error;
4520 }
4521
4522 cur_arg++;
4523 while (*(args[cur_arg])) {
Christopher Fauletbb591a12020-04-01 16:52:17 +02004524 if (strcmp(args[cur_arg], "default") == 0)
4525 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
Christopher Faulet5c288742020-03-31 08:15:58 +02004526 else if (strcmp(args[cur_arg], "addr") == 0) {
4527 int port1, port2;
4528 struct protocol *proto;
4529
4530 if (!*(args[cur_arg+1])) {
4531 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
4532 goto error;
4533 }
4534
4535 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
4536 if (!sk) {
4537 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
4538 goto error;
4539 }
4540
4541 proto = protocol_by_family(sk->ss_family);
4542 if (!proto || !proto->connect) {
4543 memprintf(errmsg, "'%s' : connect() not supported for this address family.\n",
4544 args[cur_arg]);
4545 goto error;
4546 }
4547
4548 if (port1 != port2) {
4549 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'\n",
4550 args[cur_arg], args[cur_arg+1]);
4551 goto error;
4552 }
4553
4554 cur_arg++;
4555 }
Christopher Faulet4dce5922020-03-30 13:54:42 +02004556 else if (strcmp(args[cur_arg], "port") == 0) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004557 const char *p, *end;
4558
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004559 if (!*(args[cur_arg+1])) {
Christopher Fauletb7d30092020-03-30 15:19:03 +02004560 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004561 goto error;
4562 }
4563 cur_arg++;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004564
4565 port = 0;
4566 release_sample_expr(port_expr);
4567 p = args[cur_arg]; end = p + strlen(p);
4568 port = read_uint(&p, end);
4569 if (p != end) {
4570 int idx = 0;
4571
4572 px->conf.args.ctx = ARGC_SRV;
4573 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
4574 file, line, errmsg, &px->conf.args, NULL);
4575
4576 if (!port_expr) {
4577 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
4578 goto error;
4579 }
4580 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
4581 memprintf(errmsg, "error detected while parsing port expression : "
4582 " fetch method '%s' extracts information from '%s', "
4583 "none of which is available here.\n",
4584 args[cur_arg], sample_src_names(port_expr->fetch->use));
4585 goto error;
4586 }
4587 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
4588 }
4589 else if (port > 65535 || port < 1) {
4590 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
4591 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004592 goto error;
4593 }
4594 }
4595 else if (strcmp(args[cur_arg], "comment") == 0) {
4596 if (!*(args[cur_arg+1])) {
4597 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4598 goto error;
4599 }
4600 cur_arg++;
4601 free(comment);
4602 comment = strdup(args[cur_arg]);
4603 if (!comment) {
4604 memprintf(errmsg, "out of memory");
4605 goto error;
4606 }
4607 }
4608 else if (strcmp(args[cur_arg], "send-proxy") == 0)
4609 conn_opts |= TCPCHK_OPT_SEND_PROXY;
Christopher Faulet085426a2020-03-30 13:07:02 +02004610 else if (strcmp(args[cur_arg], "via-socks4") == 0)
4611 conn_opts |= TCPCHK_OPT_SOCKS4;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004612 else if (strcmp(args[cur_arg], "linger") == 0)
4613 conn_opts |= TCPCHK_OPT_LINGER;
4614#ifdef USE_OPENSSL
4615 else if (strcmp(args[cur_arg], "ssl") == 0) {
4616 px->options |= PR_O_TCPCHK_SSL;
4617 conn_opts |= TCPCHK_OPT_SSL;
4618 }
Christopher Faulet79b31d42020-03-30 13:00:05 +02004619 else if (strcmp(args[cur_arg], "sni") == 0) {
4620 if (!*(args[cur_arg+1])) {
4621 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4622 goto error;
4623 }
4624 cur_arg++;
4625 free(sni);
4626 sni = strdup(args[cur_arg]);
4627 if (!sni) {
4628 memprintf(errmsg, "out of memory");
4629 goto error;
4630 }
4631 }
Christopher Faulet98572322020-03-30 13:16:44 +02004632 else if (strcmp(args[cur_arg], "alpn") == 0) {
4633#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
4634 free(alpn);
4635 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
4636 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
4637 goto error;
4638 }
4639 cur_arg++;
4640#else
4641 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
4642 goto error;
4643#endif
4644 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004645#endif /* USE_OPENSSL */
4646
4647 else {
Christopher Faulet5c288742020-03-31 08:15:58 +02004648 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004649#ifdef USE_OPENSSL
Christopher Faulet98572322020-03-30 13:16:44 +02004650 ", 'ssl', 'sni', 'alpn'"
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004651#endif /* USE_OPENSSL */
Christopher Faulet4dce5922020-03-30 13:54:42 +02004652 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004653 args[cur_arg]);
4654 goto error;
4655 }
4656 cur_arg++;
4657 }
4658
4659 chk = calloc(1, sizeof(*chk));
4660 if (!chk) {
4661 memprintf(errmsg, "out of memory");
4662 goto error;
4663 }
Gaetan Rivet06d963a2020-02-21 18:49:05 +01004664 chk->action = TCPCHK_ACT_CONNECT;
4665 chk->comment = comment;
4666 chk->connect.port = port;
4667 chk->connect.options = conn_opts;
Christopher Faulet79b31d42020-03-30 13:00:05 +02004668 chk->connect.sni = sni;
Christopher Faulet98572322020-03-30 13:16:44 +02004669 chk->connect.alpn = alpn;
4670 chk->connect.alpn_len= alpn_len;
Christopher Fauletb7d30092020-03-30 15:19:03 +02004671 chk->connect.port_expr= port_expr;
Christopher Faulet5c288742020-03-31 08:15:58 +02004672 if (sk)
4673 chk->connect.addr = *sk;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004674 return chk;
4675
4676 error:
Christopher Faulet98572322020-03-30 13:16:44 +02004677 free(alpn);
Christopher Faulet79b31d42020-03-30 13:00:05 +02004678 free(sni);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004679 free(comment);
Christopher Fauletb7d30092020-03-30 15:19:03 +02004680 release_sample_expr(port_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004681 return NULL;
4682}
4683
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004684static struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
Christopher Faulet5d503fc2020-03-30 20:34:34 +02004685 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004686{
4687 struct tcpcheck_rule *chk = NULL;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004688 char *comment = NULL, *data = NULL;
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004689 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004690
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004691 type = ((strcmp(args[cur_arg], "send-binary") == 0) ? TCPCHK_SEND_BINARY : TCPCHK_SEND_STRING);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004692 if (!*(args[cur_arg+1])) {
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004693 memprintf(errmsg, "'%s' expects a %s as argument",
4694 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004695 goto error;
4696 }
4697
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004698 data = args[cur_arg+1];
4699
4700 cur_arg += 2;
4701 while (*(args[cur_arg])) {
4702 if (strcmp(args[cur_arg], "comment") == 0) {
4703 if (!*(args[cur_arg+1])) {
4704 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4705 goto error;
4706 }
4707 cur_arg++;
4708 free(comment);
4709 comment = strdup(args[cur_arg]);
4710 if (!comment) {
4711 memprintf(errmsg, "out of memory");
4712 goto error;
4713 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004714 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004715 else if (strcmp(args[cur_arg], "log-format") == 0) {
4716 if (type == TCPCHK_SEND_BINARY)
4717 type = TCPCHK_SEND_BINARY_LF;
4718 else if (type == TCPCHK_SEND_STRING)
4719 type = TCPCHK_SEND_STRING_LF;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004720 }
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004721 else {
4722 memprintf(errmsg, "expects 'comment', 'log-format' but got '%s' as argument.",
4723 args[cur_arg]);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004724 goto error;
4725 }
4726 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004727 }
4728
4729 chk = calloc(1, sizeof(*chk));
4730 if (!chk) {
4731 memprintf(errmsg, "out of memory");
4732 goto error;
4733 }
Gaetan Rivet48219dc2020-02-21 18:41:28 +01004734 chk->action = TCPCHK_ACT_SEND;
4735 chk->comment = comment;
4736 chk->send.type = type;
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004737
4738 switch (chk->send.type) {
4739 case TCPCHK_SEND_STRING:
4740 chk->send.data = ist2(strdup(data), strlen(data));
4741 if (!isttest(chk->send.data)) {
4742 memprintf(errmsg, "out of memory");
4743 goto error;
4744 }
4745 break;
4746 case TCPCHK_SEND_BINARY:
4747 if (parse_binary(data, &chk->send.data.ptr, (int *)&chk->send.data.len, errmsg) == 0) {
4748 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
4749 goto error;
4750 }
4751 break;
4752 case TCPCHK_SEND_STRING_LF:
4753 case TCPCHK_SEND_BINARY_LF:
4754 LIST_INIT(&chk->send.fmt);
4755 px->conf.args.ctx = ARGC_SRV;
4756 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
4757 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
4758 goto error;
4759 }
4760 break;
Christopher Faulete5870d82020-04-15 11:32:03 +02004761 case TCPCHK_SEND_HTTP:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004762 case TCPCHK_SEND_UNDEF:
4763 goto error;
4764 }
4765
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004766 return chk;
4767
4768 error:
Christopher Fauletf50f4e92020-03-30 19:52:29 +02004769 free(chk);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004770 free(comment);
4771 return NULL;
4772}
4773
Christopher Faulete5870d82020-04-15 11:32:03 +02004774static struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
4775 const char *file, int line, char **errmsg)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004776{
4777 struct tcpcheck_rule *chk = NULL;
Christopher Faulete5870d82020-04-15 11:32:03 +02004778 struct tcpcheck_http_hdr *hdr = NULL;
4779 struct http_hdr hdrs[global.tune.max_http_hdr];
4780 char *meth = NULL, *uri = NULL, *vsn = NULL;
4781 char *body = NULL, *comment = NULL;
4782 unsigned int flags = 0;
4783 int i = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01004784
4785 cur_arg++;
4786 while (*(args[cur_arg])) {
Christopher Faulete5870d82020-04-15 11:32:03 +02004787 if (strcmp(args[cur_arg], "meth") == 0) {
4788 if (!*(args[cur_arg+1])) {
4789 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4790 goto error;
4791 }
4792 cur_arg++;
4793 meth = args[cur_arg];
4794 }
4795 else if (strcmp(args[cur_arg], "uri") == 0) {
4796 if (!*(args[cur_arg+1])) {
4797 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4798 goto error;
4799 }
4800 cur_arg++;
4801 uri = args[cur_arg];
4802 // TODO: log-format uri
4803 }
4804 else if (strcmp(args[cur_arg], "vsn") == 0) {
4805 if (!*(args[cur_arg+1])) {
4806 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4807 goto error;
4808 }
4809 cur_arg++;
4810 vsn = args[cur_arg];
4811 }
4812 else if (strcmp(args[cur_arg], "hdr") == 0) {
4813 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
4814 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
4815 goto error;
4816 }
4817 hdrs[i].n = ist2(args[cur_arg+1], strlen(args[cur_arg+1]));
4818 hdrs[i].v = ist2(args[cur_arg+2], strlen(args[cur_arg+2]));
4819 i++;
4820 cur_arg += 2;
4821 }
4822 else if (strcmp(args[cur_arg], "body") == 0) {
4823 if (!*(args[cur_arg+1])) {
4824 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4825 goto error;
4826 }
4827 cur_arg++;
4828 body = args[cur_arg];
4829 // TODO: log-format body
4830 }
4831 else if (strcmp(args[cur_arg], "comment") == 0) {
4832 if (!*(args[cur_arg+1])) {
4833 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
4834 goto error;
4835 }
4836 cur_arg++;
4837 free(comment);
4838 comment = strdup(args[cur_arg]);
4839 if (!comment) {
4840 memprintf(errmsg, "out of memory");
4841 goto error;
4842 }
4843 }
4844 else {
4845 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'hdr' and 'body' but got '%s' as argument.",
4846 args[cur_arg]);
4847 goto error;
4848 }
4849 cur_arg++;
4850 }
4851
4852 hdrs[i].n = hdrs[i].v = IST_NULL;
4853
4854 chk = calloc(1, sizeof(*chk));
4855 if (!chk) {
4856 memprintf(errmsg, "out of memory");
4857 goto error;
4858 }
4859 chk->action = TCPCHK_ACT_SEND;
4860 chk->comment = comment; comment = NULL;
4861 chk->send.type = TCPCHK_SEND_HTTP;
4862 chk->send.http.flags = flags;
4863 LIST_INIT(&chk->send.http.hdrs);
4864
4865 if (meth) {
4866 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4867 chk->send.http.meth.str.area = strdup(meth);
4868 chk->send.http.meth.str.data = strlen(meth);
4869 if (!chk->send.http.meth.str.area) {
4870 memprintf(errmsg, "out of memory");
4871 goto error;
4872 }
4873 }
4874 if (uri) {
4875 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
4876 if (!isttest(chk->send.http.uri)) {
4877 memprintf(errmsg, "out of memory");
4878 goto error;
4879 }
4880 }
4881 if (vsn) {
4882 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
4883 if (!isttest(chk->send.http.vsn)) {
4884 memprintf(errmsg, "out of memory");
4885 goto error;
4886 }
4887 }
4888 for (i = 0; hdrs[i].n.len; i++) {
4889 hdr = calloc(1, sizeof(*hdr));
4890 if (!hdr) {
4891 memprintf(errmsg, "out of memory");
4892 goto error;
4893 }
4894 LIST_INIT(&hdr->value);
4895 hdr->name = ist2(strdup(hdrs[i].n.ptr), hdrs[i].n.len);
4896 if (!hdr->name.ptr) {
4897 memprintf(errmsg, "out of memory");
4898 goto error;
4899 }
4900
4901 hdrs[i].v.ptr[hdrs[i].v.len] = '\0';
4902 if (!parse_logformat_string(hdrs[i].v.ptr, px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
4903 goto error;
4904 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
4905 hdr = NULL;
4906 }
4907
4908 if (body) {
4909 chk->send.http.body = ist2(strdup(body), strlen(body));
4910 if (!isttest(chk->send.http.body)) {
4911 memprintf(errmsg, "out of memory");
4912 goto error;
4913 }
4914 }
4915
4916 return chk;
4917
4918 error:
4919 free_tcpcheck_http_hdr(hdr);
4920 free_tcpcheck(chk, 0);
4921 free(comment);
4922 return NULL;
4923}
4924
4925static void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
4926{
4927 struct logformat_node *lf, *lfb;
4928 struct tcpcheck_http_hdr *hdr, *bhdr;
4929
4930
4931 if (new->send.http.meth.str.area) {
4932 free(old->send.http.meth.str.area);
4933 old->send.http.meth.meth = new->send.http.meth.meth;
4934 old->send.http.meth.str.area = new->send.http.meth.str.area;
4935 old->send.http.meth.str.data = new->send.http.meth.str.data;
4936 new->send.http.meth.str = BUF_NULL;
4937 }
4938
4939 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
4940 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
4941 free(old->send.http.uri.ptr);
4942 else
4943 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4944 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
4945 old->send.http.uri = new->send.http.uri;
4946 new->send.http.uri = IST_NULL;
4947 }
4948 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
4949 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
4950 free(old->send.http.uri.ptr);
4951 else
4952 free_tcpcheck_fmt(&old->send.http.uri_fmt);
4953 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
4954 LIST_INIT(&old->send.http.uri_fmt);
4955 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
4956 LIST_DEL(&lf->list);
4957 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
4958 }
4959 }
4960
4961 if (isttest(new->send.http.vsn)) {
4962 free(old->send.http.vsn.ptr);
4963 old->send.http.vsn = new->send.http.vsn;
4964 new->send.http.vsn = IST_NULL;
4965 }
4966
4967 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
4968 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
4969 LIST_DEL(&hdr->list);
4970 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
4971 }
4972
4973 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
4974 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
4975 free(old->send.http.body.ptr);
4976 else
4977 free_tcpcheck_fmt(&old->send.http.body_fmt);
4978 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
4979 old->send.http.body = new->send.http.body;
4980 new->send.http.body = IST_NULL;
4981 }
4982 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
4983 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
4984 free(old->send.http.body.ptr);
4985 else
4986 free_tcpcheck_fmt(&old->send.http.body_fmt);
4987 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
4988 LIST_INIT(&old->send.http.body_fmt);
4989 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
4990 LIST_DEL(&lf->list);
4991 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
4992 }
4993 }
4994}
4995
4996static int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
4997{
4998 struct tcpcheck_rule *r;
4999
5000 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
5001 r = get_first_tcpcheck_rule(rules);
5002 if (r && r->action == TCPCHK_ACT_CONNECT)
5003 r = get_next_tcpcheck_rule(rules, r);
5004 if (!r || r->action != TCPCHK_ACT_SEND)
5005 LIST_ADD(rules->list, &chk->list);
5006 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
5007 LIST_DEL(&r->list);
5008 free_tcpcheck(r, 0);
5009 LIST_ADD(rules->list, &chk->list);
5010 }
5011 else {
5012 tcpcheck_overwrite_send_http_rule(r, chk);
5013 free_tcpcheck(chk, 0);
5014 }
5015 }
5016 else {
5017 r = get_last_tcpcheck_rule(rules);
5018 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
5019 /* no error */;
5020 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
5021 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
5022 chk->index+1);
5023 return 0;
5024 }
5025 else if (r->action != TCPCHK_ACT_SEND && chk->action == TCPCHK_ACT_EXPECT) {
5026 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
5027 chk->index+1);
5028 return 0;
5029 }
5030 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
5031 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
5032 chk->index+1);
5033 return 0;
5034 }
5035
5036 if (chk->action == TCPCHK_ACT_SEND) {
5037 r = get_first_tcpcheck_rule(rules);
5038 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
5039 tcpcheck_overwrite_send_http_rule(r, chk);
5040 free_tcpcheck(chk, 0);
5041 LIST_DEL(&r->list);
5042 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
5043 chk = r;
5044 }
5045 }
5046 LIST_ADDQ(rules->list, &chk->list);
5047 }
5048 return 1;
5049}
5050
5051static struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
5052 const char *file, int line, char **errmsg)
5053{
5054 struct tcpcheck_rule *chk = NULL;
5055 char *comment = NULL;
5056
5057 if (!*(args[cur_arg+1])) {
5058 memprintf(errmsg, "expects a string as argument");
5059 goto error;
5060 }
5061 cur_arg++;
5062 comment = strdup(args[cur_arg]);
5063 if (!comment) {
5064 memprintf(errmsg, "out of memory");
5065 goto error;
5066 }
5067
5068 chk = calloc(1, sizeof(*chk));
5069 if (!chk) {
5070 memprintf(errmsg, "out of memory");
5071 goto error;
5072 }
5073 chk->action = TCPCHK_ACT_COMMENT;
5074 chk->comment = comment;
5075 return chk;
5076
5077 error:
5078 free(comment);
5079 return NULL;
5080}
5081
5082static struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
5083 struct list *rules, unsigned int proto,
5084 const char *file, int line, char **errmsg)
5085{
5086 struct tcpcheck_rule *prev_check, *chk = NULL;
5087 struct sample_expr *status_expr = NULL;
5088 char *str, *on_success_msg, *on_error_msg, *comment, *pattern;
5089 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
5090 enum healthcheck_status ok_st = HCHK_STATUS_L7OKD;
5091 enum healthcheck_status err_st = HCHK_STATUS_L7RSP;
5092 enum healthcheck_status tout_st = HCHK_STATUS_L7TOUT;
5093 long min_recv = -1;
5094 int inverse = 0, with_capture = 0;
5095
5096 str = on_success_msg = on_error_msg = comment = pattern = NULL;
5097 if (!*(args[cur_arg+1])) {
5098 memprintf(errmsg, "expects at least a matching pattern as arguments");
5099 goto error;
5100 }
5101
5102 cur_arg++;
5103 while (*(args[cur_arg])) {
5104 int in_pattern = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005105
5106 rescan:
5107 if (strcmp(args[cur_arg], "min-recv") == 0) {
5108 if (in_pattern) {
5109 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5110 goto error;
5111 }
5112 if (!*(args[cur_arg+1])) {
5113 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
5114 goto error;
5115 }
5116 /* Use an signed integer here because of chksize */
5117 cur_arg++;
5118 min_recv = atol(args[cur_arg]);
5119 if (min_recv < -1 || min_recv > INT_MAX) {
5120 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
5121 goto error;
5122 }
5123 }
5124 else if (*(args[cur_arg]) == '!') {
5125 in_pattern = 1;
5126 while (*(args[cur_arg]) == '!') {
5127 inverse = !inverse;
5128 args[cur_arg]++;
5129 }
5130 if (!*(args[cur_arg]))
5131 cur_arg++;
5132 goto rescan;
5133 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005134 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005135 if (type != TCPCHK_EXPECT_UNDEF) {
5136 memprintf(errmsg, "only on pattern expected");
5137 goto error;
5138 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005139 if (proto != TCPCHK_RULES_HTTP_CHK)
5140 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_REGEX);
5141 else
5142 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_REGEX_BODY);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005143
5144 if (!*(args[cur_arg+1])) {
5145 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
5146 goto error;
5147 }
5148 cur_arg++;
5149 pattern = args[cur_arg];
5150 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005151 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
5152 if (proto == TCPCHK_RULES_HTTP_CHK)
5153 goto bad_http_kw;
5154 if (type != TCPCHK_EXPECT_UNDEF) {
5155 memprintf(errmsg, "only on pattern expected");
5156 goto error;
5157 }
5158 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_REGEX_BINARY);
5159
5160 if (!*(args[cur_arg+1])) {
5161 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
5162 goto error;
5163 }
5164 cur_arg++;
5165 pattern = args[cur_arg];
5166 }
5167 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
5168 if (proto != TCPCHK_RULES_HTTP_CHK)
5169 goto bad_tcp_kw;
5170 if (type != TCPCHK_EXPECT_UNDEF) {
5171 memprintf(errmsg, "only on pattern expected");
5172 goto error;
5173 }
5174 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_REGEX_STATUS);
5175
5176 if (!*(args[cur_arg+1])) {
5177 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
5178 goto error;
5179 }
5180 cur_arg++;
5181 pattern = args[cur_arg];
5182 }
Christopher Faulet9e6ed152020-04-03 15:24:06 +02005183 else if (strcmp(args[cur_arg], "custom") == 0) {
5184 if (in_pattern) {
5185 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5186 goto error;
5187 }
5188 if (type != TCPCHK_EXPECT_UNDEF) {
5189 memprintf(errmsg, "only on pattern expected");
5190 goto error;
5191 }
5192 type = TCPCHK_EXPECT_CUSTOM;
5193 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005194 else if (strcmp(args[cur_arg], "comment") == 0) {
5195 if (in_pattern) {
5196 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5197 goto error;
5198 }
5199 if (!*(args[cur_arg+1])) {
5200 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5201 goto error;
5202 }
5203 cur_arg++;
5204 free(comment);
5205 comment = strdup(args[cur_arg]);
5206 if (!comment) {
5207 memprintf(errmsg, "out of memory");
5208 goto error;
5209 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005210 }
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005211 else if (strcmp(args[cur_arg], "on-success") == 0) {
5212 if (in_pattern) {
5213 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5214 goto error;
5215 }
5216 if (!*(args[cur_arg+1])) {
5217 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5218 goto error;
5219 }
5220 cur_arg++;
5221 free(on_success_msg);
5222 on_success_msg = strdup(args[cur_arg]);
5223 if (!on_success_msg) {
5224 memprintf(errmsg, "out of memory");
5225 goto error;
5226 }
5227 }
5228 else if (strcmp(args[cur_arg], "on-error") == 0) {
5229 if (in_pattern) {
5230 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5231 goto error;
5232 }
5233 if (!*(args[cur_arg+1])) {
5234 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5235 goto error;
5236 }
5237 cur_arg++;
5238 free(on_error_msg);
5239 on_error_msg = strdup(args[cur_arg]);
5240 if (!on_error_msg) {
5241 memprintf(errmsg, "out of memory");
5242 goto error;
5243 }
Christopher Fauletec07e382020-04-07 14:56:26 +02005244 }
5245 else if (strcmp(args[cur_arg], "ok-status") == 0) {
5246 if (in_pattern) {
5247 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5248 goto error;
5249 }
5250 if (!*(args[cur_arg+1])) {
5251 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5252 goto error;
5253 }
5254 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
5255 ok_st = HCHK_STATUS_L7OKD;
5256 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
5257 ok_st = HCHK_STATUS_L7OKCD;
5258 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
5259 ok_st = HCHK_STATUS_L6OK;
5260 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
5261 ok_st = HCHK_STATUS_L4OK;
5262 else {
5263 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
5264 args[cur_arg], args[cur_arg+1]);
5265 goto error;
5266 }
5267 cur_arg++;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005268 }
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005269 else if (strcmp(args[cur_arg], "error-status") == 0) {
5270 if (in_pattern) {
5271 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5272 goto error;
5273 }
5274 if (!*(args[cur_arg+1])) {
5275 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5276 goto error;
5277 }
5278 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
5279 err_st = HCHK_STATUS_L7RSP;
5280 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
5281 err_st = HCHK_STATUS_L7STS;
5282 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
5283 err_st = HCHK_STATUS_L6RSP;
5284 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
5285 err_st = HCHK_STATUS_L4CON;
5286 else {
5287 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
5288 args[cur_arg], args[cur_arg+1]);
5289 goto error;
5290 }
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005291 cur_arg++;
5292 }
5293 else if (strcmp(args[cur_arg], "status-code") == 0) {
5294 int idx = 0;
5295
5296 if (in_pattern) {
5297 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5298 goto error;
5299 }
5300 if (!*(args[cur_arg+1])) {
5301 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
5302 goto error;
5303 }
5304
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005305 cur_arg++;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005306 release_sample_expr(status_expr);
5307 px->conf.args.ctx = ARGC_SRV;
5308 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
5309 file, line, errmsg, &px->conf.args, NULL);
5310 if (!status_expr) {
5311 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
5312 goto error;
5313 }
5314 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
5315 memprintf(errmsg, "error detected while parsing status-code expression : "
5316 " fetch method '%s' extracts information from '%s', "
5317 "none of which is available here.\n",
5318 args[cur_arg], sample_src_names(status_expr->fetch->use));
5319 goto error;
5320 }
5321 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005322 }
5323 else if (strcmp(args[cur_arg], "tout-status") == 0) {
5324 if (in_pattern) {
5325 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
5326 goto error;
5327 }
5328 if (!*(args[cur_arg+1])) {
5329 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
5330 goto error;
5331 }
5332 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
5333 tout_st = HCHK_STATUS_L7TOUT;
5334 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
5335 tout_st = HCHK_STATUS_L6TOUT;
5336 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
5337 tout_st = HCHK_STATUS_L4TOUT;
5338 else {
5339 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
5340 args[cur_arg], args[cur_arg+1]);
5341 goto error;
5342 }
5343 cur_arg++;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005344 }
5345 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005346 if (proto == TCPCHK_RULES_HTTP_CHK) {
5347 bad_http_kw:
5348 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]status', '[!]rstatus'"
5349 " or comment but got '%s' as argument.", args[cur_arg]);
5350 }
5351 else {
5352 bad_tcp_kw:
5353 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]rbinary'"
5354 " or comment but got '%s' as argument.", args[cur_arg]);
5355 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005356 goto error;
5357 }
5358
5359 cur_arg++;
5360 }
5361
5362 if (comment) {
5363 char *p = comment;
5364
5365 while (*p) {
5366 if (*p == '\\') {
5367 p++;
5368 if (!*p || !isdigit((unsigned char)*p) ||
5369 (*p == 'x' && (!*(p+1) || !*(p+2) || !ishex(*(p+1)) || !ishex(*(p+2))))) {
5370 memprintf(errmsg, "invalid backreference in 'comment' argument");
5371 goto error;
5372 }
5373 with_capture = 1;
5374 }
5375 p++;
5376 }
5377 if (with_capture && !inverse)
5378 memprintf(errmsg, "using backreference in a positive expect comment is useless");
5379 }
5380
5381 chk = calloc(1, sizeof(*chk));
5382 if (!chk) {
5383 memprintf(errmsg, "out of memory");
5384 goto error;
5385 }
5386 chk->action = TCPCHK_ACT_EXPECT;
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005387 LIST_INIT(&chk->expect.onerror_fmt);
5388 LIST_INIT(&chk->expect.onsuccess_fmt);
5389 chk->comment = comment; comment = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005390 chk->expect.type = type;
5391 chk->expect.min_recv = min_recv;
Christopher Faulet12d57402020-04-10 09:58:42 +02005392 chk->expect.flags |= (inverse ? TCPCHK_EXPT_FL_INV : 0);
5393 chk->expect.flags |= (with_capture ? TCPCHK_EXPT_FL_CAP : 0);
Christopher Fauletec07e382020-04-07 14:56:26 +02005394 chk->expect.ok_status = ok_st;
Christopher Fauletcf80f2f2020-04-01 11:04:52 +02005395 chk->expect.err_status = err_st;
5396 chk->expect.tout_status = tout_st;
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005397 chk->expect.status_expr = status_expr; status_expr = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005398
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005399 if (on_success_msg) {
5400 px->conf.args.ctx = ARGC_SRV;
5401 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
5402 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
5403 goto error;
5404 }
5405 free(on_success_msg);
5406 on_success_msg = NULL;
5407 }
5408 if (on_error_msg) {
5409 px->conf.args.ctx = ARGC_SRV;
5410 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
5411 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
5412 goto error;
5413 }
5414 free(on_error_msg);
5415 on_error_msg = NULL;
5416 }
5417
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005418 switch (chk->expect.type) {
5419 case TCPCHK_EXPECT_STRING:
Christopher Faulete5870d82020-04-15 11:32:03 +02005420 case TCPCHK_EXPECT_HTTP_STATUS:
5421 case TCPCHK_EXPECT_HTTP_BODY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02005422 chk->expect.data = ist2(strdup(pattern), strlen(pattern));
5423 if (!chk->expect.data.ptr) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005424 memprintf(errmsg, "out of memory");
5425 goto error;
5426 }
5427 break;
5428 case TCPCHK_EXPECT_BINARY:
Christopher Fauletf930e4c2020-04-10 09:20:02 +02005429 if (parse_binary(pattern, &chk->expect.data.ptr, (int *)&chk->expect.data.len, errmsg) == 0) {
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005430 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
5431 goto error;
5432 }
5433 case TCPCHK_EXPECT_REGEX:
5434 case TCPCHK_EXPECT_REGEX_BINARY:
Christopher Faulete5870d82020-04-15 11:32:03 +02005435 case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
5436 case TCPCHK_EXPECT_HTTP_REGEX_BODY:
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005437 chk->expect.regex = regex_comp(pattern, 1, with_capture, errmsg);
5438 if (!chk->expect.regex)
5439 goto error;
5440 break;
Christopher Faulet9e6ed152020-04-03 15:24:06 +02005441 case TCPCHK_EXPECT_CUSTOM:
5442 chk->expect.custom = NULL; /* Must be defined by the caller ! */
5443 break;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005444 case TCPCHK_EXPECT_UNDEF:
5445 free(chk);
5446 memprintf(errmsg, "pattern not found");
5447 goto error;
5448 }
5449
5450 /* All tcp-check expect points back to the first inverse expect rule in
5451 * a chain of one or more expect rule, potentially itself.
5452 */
5453 chk->expect.head = chk;
5454 list_for_each_entry_rev(prev_check, rules, list) {
5455 if (prev_check->action == TCPCHK_ACT_EXPECT) {
Christopher Faulet12d57402020-04-10 09:58:42 +02005456 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005457 chk->expect.head = prev_check;
5458 continue;
5459 }
Gaetan Rivet0c39ecc2020-02-24 17:34:11 +01005460 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005461 break;
5462 }
5463 return chk;
5464
5465 error:
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005466 free_tcpcheck(chk, 0);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005467 free(str);
5468 free(comment);
Christopher Fauletbe52b4d2020-04-01 16:30:22 +02005469 free(on_success_msg);
5470 free(on_error_msg);
Christopher Faulet98cc57c2020-04-01 20:52:31 +02005471 release_sample_expr(status_expr);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005472 return NULL;
5473}
5474
5475/* Parses the "tcp-check" proxy keyword */
5476static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
5477 struct proxy *defpx, const char *file, int line,
5478 char **errmsg)
5479{
Christopher Faulet404f9192020-04-09 23:13:54 +02005480 struct tcpcheck_ruleset *rs = NULL;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005481 struct tcpcheck_rule *chk = NULL;
Gaetan Rivet5301b012020-02-25 17:19:17 +01005482 int index, cur_arg, ret = 0;
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005483
5484 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5485 ret = 1;
5486
Christopher Faulet404f9192020-04-09 23:13:54 +02005487 /* Deduce the ruleset name from the proxy info */
5488 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5489 ((curpx == defpx) ? "defaults" : curpx->id),
5490 curpx->conf.file, curpx->conf.line);
Christopher Faulet5d503fc2020-03-30 20:34:34 +02005491
Christopher Faulet404f9192020-04-09 23:13:54 +02005492 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5493 if (rs == NULL) {
5494 rs = tcpcheck_ruleset_create(b_orig(&trash));
5495 if (rs == NULL) {
5496 memprintf(errmsg, "out of memory.\n");
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005497 goto error;
5498 }
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005499 }
5500
Gaetan Rivet5301b012020-02-25 17:19:17 +01005501 index = 0;
Christopher Faulet404f9192020-04-09 23:13:54 +02005502 if (!LIST_ISEMPTY(&rs->rules)) {
5503 chk = LIST_PREV(&rs->rules, typeof(chk), list);
Gaetan Rivet5301b012020-02-25 17:19:17 +01005504 index = chk->index + 1;
5505 }
5506
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005507 cur_arg = 1;
5508 if (strcmp(args[cur_arg], "connect") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005509 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005510 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005511 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005512 else if (strcmp(args[cur_arg], "expect") == 0)
Christopher Faulete5870d82020-04-15 11:32:03 +02005513 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005514 else if (strcmp(args[cur_arg], "comment") == 0)
Christopher Faulet404f9192020-04-09 23:13:54 +02005515 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005516 else {
Gaetan Rivet707b52f2020-02-21 18:14:59 +01005517 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
5518
5519 if (!kw) {
5520 action_kw_tcp_check_build_list(&trash);
5521 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
5522 "%s%s. but got '%s'",
5523 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5524 goto error;
5525 }
Christopher Faulet404f9192020-04-09 23:13:54 +02005526 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005527 }
5528
5529 if (!chk) {
5530 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5531 goto error;
5532 }
5533 ret = (*errmsg != NULL); /* Handle warning */
5534
5535 /* No error: add the tcp-check rule in the list */
Gaetan Rivet5301b012020-02-25 17:19:17 +01005536 chk->index = index;
Christopher Faulet404f9192020-04-09 23:13:54 +02005537 LIST_ADDQ(&rs->rules, &chk->list);
5538
5539 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5540 !(curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK)) {
5541 /* Use this ruleset if the proxy already has tcp-check enabled */
5542 curpx->tcpcheck_rules.list = &rs->rules;
5543 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
5544 }
5545 else {
5546 /* mark this ruleset as unused for now */
5547 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
5548 }
5549
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005550 return ret;
5551
5552 error:
Christopher Faulet404f9192020-04-09 23:13:54 +02005553 free_tcpcheck(chk, 0);
5554 tcpcheck_ruleset_release(rs);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01005555 return -1;
5556}
5557
Christopher Faulet51b129f2020-04-09 15:54:18 +02005558/* Parses the "http-check" proxy keyword */
5559static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
5560 struct proxy *defpx, const char *file, int line,
5561 char **errmsg)
5562{
Christopher Faulete5870d82020-04-15 11:32:03 +02005563 struct tcpcheck_ruleset *rs = NULL;
5564 struct tcpcheck_rule *chk = NULL;
5565 int index, cur_arg, ret = 0;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005566
5567 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
5568 ret = 1;
5569
5570 cur_arg = 1;
5571 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
5572 /* enable a graceful server shutdown on an HTTP 404 response */
5573 curpx->options |= PR_O_DISABLE404;
5574 if (too_many_args(1, args, errmsg, NULL))
5575 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005576 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005577 }
5578 else if (strcmp(args[cur_arg], "send-state") == 0) {
5579 /* enable emission of the apparent state of a server in HTTP checks */
5580 curpx->options2 |= PR_O2_CHK_SNDST;
5581 if (too_many_args(1, args, errmsg, NULL))
5582 goto error;
Christopher Faulete5870d82020-04-15 11:32:03 +02005583 goto out;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005584 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005585
Christopher Faulete5870d82020-04-15 11:32:03 +02005586 /* Deduce the ruleset name from the proxy info */
5587 chunk_printf(&trash, "*http-check-%s_%s-%d",
5588 ((curpx == defpx) ? "defaults" : curpx->id),
5589 curpx->conf.file, curpx->conf.line);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005590
Christopher Faulete5870d82020-04-15 11:32:03 +02005591 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5592 if (rs == NULL) {
5593 rs = tcpcheck_ruleset_create(b_orig(&trash));
5594 if (rs == NULL) {
5595 memprintf(errmsg, "out of memory.\n");
5596 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005597 }
5598 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005599
Christopher Faulete5870d82020-04-15 11:32:03 +02005600 index = 0;
5601 if (!LIST_ISEMPTY(&rs->rules)) {
5602 chk = LIST_PREV(&rs->rules, typeof(chk), list);
5603 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
5604 index = chk->index + 1;
5605 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005606
Christopher Faulete5870d82020-04-15 11:32:03 +02005607 if (strcmp(args[cur_arg], "connect") == 0)
5608 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5609 else if (strcmp(args[cur_arg], "send") == 0)
5610 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5611 else if (strcmp(args[cur_arg], "expect") == 0)
5612 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
5613 file, line, errmsg);
5614 else if (strcmp(args[cur_arg], "comment") == 0)
5615 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
5616 else {
5617 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005618
Christopher Faulete5870d82020-04-15 11:32:03 +02005619 if (!kw) {
5620 action_kw_tcp_check_build_list(&trash);
5621 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
5622 " 'send', 'expect'%s%s. but got '%s'",
5623 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
5624 goto error;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005625 }
Christopher Faulete5870d82020-04-15 11:32:03 +02005626 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
5627 }
Christopher Faulet51b129f2020-04-09 15:54:18 +02005628
Christopher Faulete5870d82020-04-15 11:32:03 +02005629 if (!chk) {
5630 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5631 goto error;
5632 }
5633 ret = (*errmsg != NULL); /* Handle warning */
5634
5635 chk->index = index;
5636 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
5637 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
5638 /* Use this ruleset if the proxy already has http-check enabled */
5639 curpx->tcpcheck_rules.list = &rs->rules;
5640 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
5641 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
5642 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
5643 curpx->tcpcheck_rules.list = NULL;
Christopher Faulet51b129f2020-04-09 15:54:18 +02005644 goto error;
5645 }
5646 }
5647 else {
Christopher Faulete5870d82020-04-15 11:32:03 +02005648 /* mark this ruleset as unused for now */
5649 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
5650 LIST_ADDQ(&rs->rules, &chk->list);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005651 }
5652
Christopher Faulete5870d82020-04-15 11:32:03 +02005653 out:
Christopher Faulet51b129f2020-04-09 15:54:18 +02005654 return ret;
5655
5656 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02005657 free_tcpcheck(chk, 0);
5658 tcpcheck_ruleset_release(rs);
Christopher Faulet51b129f2020-04-09 15:54:18 +02005659 return -1;
5660}
5661
Christopher Faulete9111b62020-04-09 18:12:08 +02005662/* Parses the "external-check" proxy keyword */
5663static int proxy_parse_extcheck(char **args, int section, struct proxy *curpx,
5664 struct proxy *defpx, const char *file, int line,
5665 char **errmsg)
5666{
5667 int cur_arg, ret = 0;
5668
5669 cur_arg = 1;
5670 if (!*(args[cur_arg])) {
5671 memprintf(errmsg, "missing argument after '%s'.\n", args[0]);
5672 goto error;
5673 }
5674
5675 if (strcmp(args[cur_arg], "command") == 0) {
5676 if (too_many_args(2, args, errmsg, NULL))
5677 goto error;
5678 if (!*(args[cur_arg+1])) {
5679 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5680 goto error;
5681 }
5682 free(curpx->check_command);
5683 curpx->check_command = strdup(args[cur_arg+1]);
5684 }
5685 else if (strcmp(args[cur_arg], "path") == 0) {
5686 if (too_many_args(2, args, errmsg, NULL))
5687 goto error;
5688 if (!*(args[cur_arg+1])) {
5689 memprintf(errmsg, "missing argument after '%s'.", args[cur_arg]);
5690 goto error;
5691 }
5692 free(curpx->check_path);
5693 curpx->check_path = strdup(args[cur_arg+1]);
5694 }
5695 else {
5696 memprintf(errmsg, "'%s' only supports 'command' and 'path'. but got '%s'.",
5697 args[0], args[1]);
5698 goto error;
5699 }
5700
5701 ret = (*errmsg != NULL); /* Handle warning */
5702 return ret;
5703
5704error:
5705 return -1;
5706}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005707
Christopher Faulet430e4802020-04-09 15:28:16 +02005708/* Parses the "option tcp-check" proxy keyword */
5709int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5710 const char *file, int line)
5711{
Christopher Faulet404f9192020-04-09 23:13:54 +02005712 struct tcpcheck_ruleset *rs = NULL;
Christopher Faulet430e4802020-04-09 15:28:16 +02005713 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5714 int err_code = 0;
5715
5716 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5717 err_code |= ERR_WARN;
5718
5719 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5720 goto out;
5721
Christopher Faulet404f9192020-04-09 23:13:54 +02005722 curpx->options2 &= ~PR_O2_CHK_ANY;
5723 curpx->options2 |= PR_O2_TCPCHK_CHK;
5724
5725 if (!(rules->flags & TCPCHK_RULES_PROTO_CHK)) {
5726 /* If a tcp-check rulesset is already set, do nothing */
5727 if (rules->list)
5728 goto out;
5729
5730 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5731 * get it.
5732 */
5733 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5734 goto curpx_ruleset;
5735
5736 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5737 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5738 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5739 if (rs)
5740 goto ruleset_found;
Christopher Faulet430e4802020-04-09 15:28:16 +02005741 }
5742
Christopher Faulet404f9192020-04-09 23:13:54 +02005743 curpx_ruleset:
5744 /* Deduce the ruleset name from the proxy info */
5745 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5746 ((curpx == defpx) ? "defaults" : curpx->id),
5747 curpx->conf.file, curpx->conf.line);
5748
5749 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
5750 if (rs == NULL) {
5751 rs = tcpcheck_ruleset_create(b_orig(&trash));
5752 if (rs == NULL) {
Christopher Faulet430e4802020-04-09 15:28:16 +02005753 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5754 goto error;
5755 }
Christopher Faulet430e4802020-04-09 15:28:16 +02005756 }
5757
Christopher Faulet404f9192020-04-09 23:13:54 +02005758 ruleset_found:
5759 free_tcpcheck_vars(&rules->preset_vars);
5760 rules->list = NULL;
5761 rules->flags = 0;
5762
Christopher Faulet404f9192020-04-09 23:13:54 +02005763 rules->list = &rs->rules;
Christopher Faulet430e4802020-04-09 15:28:16 +02005764
5765 out:
5766 return err_code;
5767
5768 error:
5769 err_code |= ERR_ALERT | ERR_FATAL;
5770 goto out;
5771}
Christopher Faulet33f05df2020-04-01 11:08:50 +02005772
5773/* Parses the "option redis-check" proxy keyword */
5774int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5775 const char *file, int line)
5776{
5777 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
5778 static char *redis_res = "+PONG\r\n";
5779
5780 struct tcpcheck_ruleset *rs = NULL;
5781 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5782 struct tcpcheck_rule *chk;
5783 char *errmsg = NULL;
5784 int err_code = 0;
5785
5786 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5787 err_code |= ERR_WARN;
5788
5789 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5790 goto out;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005791
5792 curpx->options2 &= ~PR_O2_CHK_ANY;
5793 curpx->options2 |= PR_O2_TCPCHK_CHK;
5794
5795 free_tcpcheck_vars(&rules->preset_vars);
5796 rules->list = NULL;
5797 rules->flags = 0;
5798
5799 rs = tcpcheck_ruleset_lookup("*redis-check");
5800 if (rs)
5801 goto ruleset_found;
5802
5803 rs = tcpcheck_ruleset_create("*redis-check");
5804 if (rs == NULL) {
5805 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5806 goto error;
5807 }
5808
5809 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
5810 1, curpx, &rs->rules, file, line, &errmsg);
5811 if (!chk) {
5812 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5813 goto error;
5814 }
5815 chk->index = 0;
5816 LIST_ADDQ(&rs->rules, &chk->list);
5817
5818 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
5819 "error-status", "L7STS",
5820 "on-error", "%[check.payload(),cut_crlf]",
5821 "on-success", "Redis server is ok",
5822 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005823 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
Christopher Faulet33f05df2020-04-01 11:08:50 +02005824 if (!chk) {
5825 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5826 goto error;
5827 }
5828 chk->index = 1;
5829 LIST_ADDQ(&rs->rules, &chk->list);
5830
5831 LIST_ADDQ(&tcpchecks_list, &rs->list);
5832
5833 ruleset_found:
5834 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005835 rules->flags |= TCPCHK_RULES_REDIS_CHK;
Christopher Faulet33f05df2020-04-01 11:08:50 +02005836
5837 out:
5838 free(errmsg);
5839 return err_code;
5840
5841 error:
5842 tcpcheck_ruleset_release(rs);
5843 err_code |= ERR_ALERT | ERR_FATAL;
5844 goto out;
5845}
5846
Christopher Faulet811f78c2020-04-01 11:10:27 +02005847
5848/* Parses the "option ssl-hello-chk" proxy keyword */
5849int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5850 const char *file, int line)
5851{
5852 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
5853 * ssl-hello-chk option to ensure that the remote server speaks SSL.
5854 *
5855 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
5856 */
5857 static char sslv3_client_hello[] = {
5858 "16" /* ContentType : 0x16 = Hanshake */
5859 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
5860 "0079" /* ContentLength : 0x79 bytes after this one */
5861 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
5862 "000075" /* HandshakeLength : 0x75 bytes after this one */
5863 "0300" /* Hello Version : 0x0300 = v3 */
5864 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
5865 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
5866 "00" /* Session ID length : empty (no session ID) */
5867 "004E" /* Cipher Suite Length : 78 bytes after this one */
5868 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
5869 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
5870 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
5871 "000D" "000E" "000F" "0010" /* various bit lengths, */
5872 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
5873 "0015" "0016" "0017" "0018"
5874 "0019" "001A" "001B" "002F"
5875 "0030" "0031" "0032" "0033"
5876 "0034" "0035" "0036" "0037"
5877 "0038" "0039" "003A"
5878 "01" /* Compression Length : 0x01 = 1 byte for types */
5879 "00" /* Compression Type : 0x00 = NULL compression */
5880 };
5881
5882 struct tcpcheck_ruleset *rs = NULL;
5883 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5884 struct tcpcheck_rule *chk;
5885 char *errmsg = NULL;
5886 int err_code = 0;
5887
5888 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5889 err_code |= ERR_WARN;
5890
5891 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5892 goto out;
5893
Christopher Faulet811f78c2020-04-01 11:10:27 +02005894 curpx->options2 &= ~PR_O2_CHK_ANY;
5895 curpx->options2 |= PR_O2_TCPCHK_CHK;
5896
5897 free_tcpcheck_vars(&rules->preset_vars);
5898 rules->list = NULL;
5899 rules->flags = 0;
5900
5901 rs = tcpcheck_ruleset_lookup("*ssl-hello-check");
5902 if (rs)
5903 goto ruleset_found;
5904
5905 rs = tcpcheck_ruleset_create("*ssl-hello-check");
5906 if (rs == NULL) {
5907 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5908 goto error;
5909 }
5910
5911 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", sslv3_client_hello, "log-format", ""},
5912 1, curpx, &rs->rules, file, line, &errmsg);
5913 if (!chk) {
5914 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5915 goto error;
5916 }
5917 chk->index = 0;
5918 LIST_ADDQ(&rs->rules, &chk->list);
5919
5920 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
Christopher Fauletec07e382020-04-07 14:56:26 +02005921 "min-recv", "5", "ok-status", "L6OK",
Christopher Faulet811f78c2020-04-01 11:10:27 +02005922 "error-status", "L6RSP", "tout-status", "L6TOUT",
5923 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02005924 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
Christopher Faulet811f78c2020-04-01 11:10:27 +02005925 if (!chk) {
5926 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
5927 goto error;
5928 }
5929 chk->index = 1;
5930 LIST_ADDQ(&rs->rules, &chk->list);
5931
5932 LIST_ADDQ(&tcpchecks_list, &rs->list);
5933
5934 ruleset_found:
5935 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02005936 rules->flags |= TCPCHK_RULES_SSL3_CHK;
Christopher Faulet811f78c2020-04-01 11:10:27 +02005937
5938 out:
5939 free(errmsg);
5940 return err_code;
5941
5942 error:
5943 tcpcheck_ruleset_release(rs);
5944 err_code |= ERR_ALERT | ERR_FATAL;
5945 goto out;
5946}
5947
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005948/* Parses the "option smtpchk" proxy keyword */
5949int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
5950 const char *file, int line)
5951{
5952 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
5953
5954 struct tcpcheck_ruleset *rs = NULL;
5955 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5956 struct tcpcheck_rule *chk;
5957 struct tcpcheck_var *var = NULL;
5958 char *cmd = NULL, *errmsg = NULL;
5959 int err_code = 0;
5960
5961 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5962 err_code |= ERR_WARN;
5963
5964 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
5965 goto out;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02005966
5967 curpx->options2 &= ~PR_O2_CHK_ANY;
5968 curpx->options2 |= PR_O2_TCPCHK_CHK;
5969
5970 free_tcpcheck_vars(&rules->preset_vars);
5971 rules->list = NULL;
5972 rules->flags = 0;
5973
5974 cur_arg += 2;
5975 if (*args[cur_arg] && *args[cur_arg+1] &&
5976 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
5977 cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
5978 if (cmd)
5979 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
5980 }
5981 else {
5982 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
5983 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
5984 cmd = strdup("HELO localhost");
5985 }
5986
5987 var = tcpcheck_var_create("check.smtp_cmd");
5988 if (cmd == NULL || var == NULL) {
5989 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5990 goto error;
5991 }
5992 var->data.type = SMP_T_STR;
5993 var->data.u.str.area = cmd;
5994 var->data.u.str.data = strlen(cmd);
5995 LIST_INIT(&var->list);
5996 LIST_ADDQ(&rules->preset_vars, &var->list);
5997 cmd = NULL;
5998 var = NULL;
5999
6000 rs = tcpcheck_ruleset_lookup("*smtp-check");
6001 if (rs)
6002 goto ruleset_found;
6003
6004 rs = tcpcheck_ruleset_create("*smtp-check");
6005 if (rs == NULL) {
6006 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6007 goto error;
6008 }
6009
6010 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6011 1, curpx, &rs->rules, file, line, &errmsg);
6012 if (!chk) {
6013 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6014 goto error;
6015 }
6016 chk->index = 0;
6017 LIST_ADDQ(&rs->rules, &chk->list);
6018
6019 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
6020 "min-recv", "4",
6021 "error-status", "L7RSP",
6022 "on-error", "%[check.payload(),cut_crlf]",
6023 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006024 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006025 if (!chk) {
6026 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6027 goto error;
6028 }
6029 chk->index = 1;
6030 LIST_ADDQ(&rs->rules, &chk->list);
6031
6032 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
6033 "min-recv", "4",
6034 "error-status", "L7STS",
6035 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6036 "status-code", "check.payload(0,3)",
6037 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006038 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006039 if (!chk) {
6040 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6041 goto error;
6042 }
6043 chk->index = 2;
6044 LIST_ADDQ(&rs->rules, &chk->list);
6045
6046 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
6047 1, curpx, &rs->rules, file, line, &errmsg);
6048 if (!chk) {
6049 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6050 goto error;
6051 }
6052 chk->index = 3;
6053 LIST_ADDQ(&rs->rules, &chk->list);
6054
6055 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
6056 "min-recv", "4",
6057 "error-status", "L7STS",
6058 "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6059 "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
6060 "status-code", "check.payload(0,3)",
6061 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006062 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006063 if (!chk) {
6064 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6065 goto error;
6066 }
6067 chk->index = 4;
6068 LIST_ADDQ(&rs->rules, &chk->list);
6069
6070 LIST_ADDQ(&tcpchecks_list, &rs->list);
6071
6072 ruleset_found:
6073 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006074 rules->flags |= TCPCHK_RULES_SMTP_CHK;
Christopher Fauletfbcc77c2020-04-01 20:54:05 +02006075
6076 out:
6077 free(errmsg);
6078 return err_code;
6079
6080 error:
6081 free(cmd);
6082 free(var);
6083 free_tcpcheck_vars(&rules->preset_vars);
6084 tcpcheck_ruleset_release(rs);
6085 err_code |= ERR_ALERT | ERR_FATAL;
6086 goto out;
6087}
Christopher Faulet811f78c2020-04-01 11:10:27 +02006088
Christopher Fauletce355072020-04-02 11:44:39 +02006089/* Parses the "option pgsql-check" proxy keyword */
6090int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6091 const char *file, int line)
6092{
6093 static char pgsql_req[] = {
6094 "%[var(check.plen),htonl,hex]" /* The packet length*/
6095 "00030000" /* the version 3.0 */
6096 "7573657200" /* "user" key */
6097 "%[var(check.username),hex]00" /* the username */
6098 "00"
6099 };
6100
6101 struct tcpcheck_ruleset *rs = NULL;
6102 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6103 struct tcpcheck_rule *chk;
6104 struct tcpcheck_var *var = NULL;
6105 char *user = NULL, *errmsg = NULL;
6106 size_t packetlen = 0;
6107 int err_code = 0;
6108
6109 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6110 err_code |= ERR_WARN;
6111
6112 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
6113 goto out;
6114
Christopher Fauletce355072020-04-02 11:44:39 +02006115 curpx->options2 &= ~PR_O2_CHK_ANY;
6116 curpx->options2 |= PR_O2_TCPCHK_CHK;
6117
6118 free_tcpcheck_vars(&rules->preset_vars);
6119 rules->list = NULL;
6120 rules->flags = 0;
6121
6122 cur_arg += 2;
6123 if (!*args[cur_arg] || !*args[cur_arg+1]) {
6124 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
6125 file, line, args[0], args[1]);
6126 goto error;
6127 }
6128 if (strcmp(args[cur_arg], "user") == 0) {
6129 packetlen = 15 + strlen(args[cur_arg+1]);
6130 user = strdup(args[cur_arg+1]);
6131
6132 var = tcpcheck_var_create("check.username");
6133 if (user == NULL || var == NULL) {
6134 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6135 goto error;
6136 }
6137 var->data.type = SMP_T_STR;
6138 var->data.u.str.area = user;
6139 var->data.u.str.data = strlen(user);
6140 LIST_INIT(&var->list);
6141 LIST_ADDQ(&rules->preset_vars, &var->list);
6142 user = NULL;
6143 var = NULL;
6144
6145 var = tcpcheck_var_create("check.plen");
6146 if (var == NULL) {
6147 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6148 goto error;
6149 }
6150 var->data.type = SMP_T_SINT;
6151 var->data.u.sint = packetlen;
6152 LIST_INIT(&var->list);
6153 LIST_ADDQ(&rules->preset_vars, &var->list);
6154 var = NULL;
6155 }
6156 else {
6157 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
6158 file, line, args[0], args[1]);
6159 goto error;
6160 }
6161
6162 rs = tcpcheck_ruleset_lookup("*pgsql-check");
6163 if (rs)
6164 goto ruleset_found;
6165
6166 rs = tcpcheck_ruleset_create("*pgsql-check");
6167 if (rs == NULL) {
6168 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6169 goto error;
6170 }
6171
6172 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6173 1, curpx, &rs->rules, file, line, &errmsg);
6174 if (!chk) {
6175 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6176 goto error;
6177 }
6178 chk->index = 0;
6179 LIST_ADDQ(&rs->rules, &chk->list);
6180
6181 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""},
6182 1, curpx, &rs->rules, file, line, &errmsg);
6183 if (!chk) {
6184 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6185 goto error;
6186 }
6187 chk->index = 1;
6188 LIST_ADDQ(&rs->rules, &chk->list);
6189
6190 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
6191 "min-recv", "5",
6192 "error-status", "L7RSP",
6193 "on-error", "%[check.payload(6,0)]",
6194 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006195 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006196 if (!chk) {
6197 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6198 goto error;
6199 }
6200 chk->index = 2;
6201 LIST_ADDQ(&rs->rules, &chk->list);
6202
6203 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000",
6204 "min-recv", "9",
6205 "error-status", "L7STS",
6206 "on-success", "PostgreSQL server is ok",
6207 "on-error", "PostgreSQL unknown error",
6208 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006209 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
Christopher Fauletce355072020-04-02 11:44:39 +02006210 if (!chk) {
6211 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6212 goto error;
6213 }
6214 chk->index = 3;
6215 LIST_ADDQ(&rs->rules, &chk->list);
6216
6217 LIST_ADDQ(&tcpchecks_list, &rs->list);
6218
6219 ruleset_found:
6220 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006221 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
Christopher Fauletce355072020-04-02 11:44:39 +02006222
6223 out:
6224 free(errmsg);
6225 return err_code;
6226
6227 error:
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006228 free(user);
6229 free(var);
6230 free_tcpcheck_vars(&rules->preset_vars);
6231 tcpcheck_ruleset_release(rs);
6232 err_code |= ERR_ALERT | ERR_FATAL;
6233 goto out;
6234}
6235
6236
6237/* Parses the "option mysql-check" proxy keyword */
6238int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6239 const char *file, int line)
6240{
6241 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
6242 * const char mysql40_client_auth_pkt[] = {
6243 * "\x0e\x00\x00" // packet length
6244 * "\x01" // packet number
6245 * "\x00\x00" // client capabilities
6246 * "\x00\x00\x01" // max packet
6247 * "haproxy\x00" // username (null terminated string)
6248 * "\x00" // filler (always 0x00)
6249 * "\x01\x00\x00" // packet length
6250 * "\x00" // packet number
6251 * "\x01" // COM_QUIT command
6252 * };
6253 */
6254 static char mysql40_rsname[] = "*mysql40-check";
6255 static char mysql40_req[] = {
6256 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6257 "0080" /* client capabilities */
6258 "000001" /* max packet */
6259 "%[var(check.username),hex]00" /* the username */
6260 "00" /* filler (always 0x00) */
6261 "010000" /* packet length*/
6262 "00" /* sequence ID */
6263 "01" /* COM_QUIT command */
6264 };
6265
6266 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
6267 * const char mysql41_client_auth_pkt[] = {
6268 * "\x0e\x00\x00\" // packet length
6269 * "\x01" // packet number
6270 * "\x00\x00\x00\x00" // client capabilities
6271 * "\x00\x00\x00\x01" // max packet
6272 * "\x21" // character set (UTF-8)
6273 * char[23] // All zeroes
6274 * "haproxy\x00" // username (null terminated string)
6275 * "\x00" // filler (always 0x00)
6276 * "\x01\x00\x00" // packet length
6277 * "\x00" // packet number
6278 * "\x01" // COM_QUIT command
6279 * };
6280 */
6281 static char mysql41_rsname[] = "*mysql41-check";
6282 static char mysql41_req[] = {
6283 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
6284 "00820000" /* client capabilities */
6285 "00800001" /* max packet */
6286 "21" /* character set (UTF-8) */
6287 "000000000000000000000000" /* 23 bytes, al zeroes */
6288 "0000000000000000000000"
6289 "%[var(check.username),hex]00" /* the username */
6290 "00" /* filler (always 0x00) */
6291 "010000" /* packet length*/
6292 "00" /* sequence ID */
6293 "01" /* COM_QUIT command */
6294 };
6295
6296 struct tcpcheck_ruleset *rs = NULL;
6297 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6298 struct tcpcheck_rule *chk;
6299 struct tcpcheck_var *var = NULL;
6300 char *mysql_rsname = "*mysql-check";
6301 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
6302 int index = 0, err_code = 0;
6303
6304 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6305 err_code |= ERR_WARN;
6306
6307 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6308 goto out;
6309
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006310 curpx->options2 &= ~PR_O2_CHK_ANY;
6311 curpx->options2 |= PR_O2_TCPCHK_CHK;
6312
6313 free_tcpcheck_vars(&rules->preset_vars);
6314 rules->list = NULL;
6315 rules->flags = 0;
6316
6317 cur_arg += 2;
6318 if (*args[cur_arg]) {
6319 char *user;
6320 int packetlen, userlen;
6321
6322 if (strcmp(args[cur_arg], "user") != 0) {
6323 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
6324 file, line, args[0], args[1], args[cur_arg]);
6325 goto error;
6326 }
6327
6328 if (*(args[cur_arg+1]) == 0) {
6329 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
6330 file, line, args[0], args[1], args[cur_arg]);
6331 goto error;
6332 }
6333
6334 hdr = calloc(4, sizeof(*hdr));
6335 user = strdup(args[cur_arg+1]);
6336 userlen = strlen(args[cur_arg+1]);
6337
6338 if (hdr == NULL || user == NULL) {
6339 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6340 goto error;
6341 }
6342
6343 if (*args[cur_arg+2]) {
6344 if (strcmp(args[cur_arg+2], "post-41") != 0) {
6345 ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
6346 file, line, args[cur_arg], args[cur_arg+2]);
6347 goto error;
6348 }
6349 packetlen = userlen + 7 + 27;
6350 mysql_req = mysql41_req;
6351 mysql_rsname = mysql41_rsname;
6352 }
6353 else {
6354 packetlen = userlen + 7;
6355 mysql_req = mysql40_req;
6356 mysql_rsname = mysql40_rsname;
6357 }
6358
6359 hdr[0] = (unsigned char)(packetlen & 0xff);
6360 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
6361 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
6362 hdr[3] = 1;
6363
6364 var = tcpcheck_var_create("check.header");
6365 if (var == NULL) {
6366 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6367 goto error;
6368 }
6369 var->data.type = SMP_T_STR;
6370 var->data.u.str.area = hdr;
6371 var->data.u.str.data = 4;
6372 LIST_INIT(&var->list);
6373 LIST_ADDQ(&rules->preset_vars, &var->list);
6374 hdr = NULL;
6375 var = NULL;
6376
6377 var = tcpcheck_var_create("check.username");
6378 if (var == NULL) {
6379 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6380 goto error;
6381 }
6382 var->data.type = SMP_T_STR;
6383 var->data.u.str.area = user;
6384 var->data.u.str.data = strlen(user);
6385 LIST_INIT(&var->list);
6386 LIST_ADDQ(&rules->preset_vars, &var->list);
6387 user = NULL;
6388 var = NULL;
6389 }
6390
6391 rs = tcpcheck_ruleset_lookup(mysql_rsname);
6392 if (rs)
6393 goto ruleset_found;
6394
6395 rs = tcpcheck_ruleset_create(mysql_rsname);
6396 if (rs == NULL) {
6397 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6398 goto error;
6399 }
6400
6401 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
6402 1, curpx, &rs->rules, file, line, &errmsg);
6403 if (!chk) {
6404 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6405 goto error;
6406 }
6407 chk->index = index++;
6408 LIST_ADDQ(&rs->rules, &chk->list);
6409
6410 if (mysql_req) {
6411 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_req, "log-format", ""},
6412 1, curpx, &rs->rules, file, line, &errmsg);
6413 if (!chk) {
6414 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6415 goto error;
6416 }
6417 chk->index = index++;
6418 LIST_ADDQ(&rs->rules, &chk->list);
6419 }
6420
6421 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006422 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006423 if (!chk) {
6424 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6425 goto error;
6426 }
6427 chk->expect.custom = tcpcheck_mysql_expect_iniths;
6428 chk->index = index++;
6429 LIST_ADDQ(&rs->rules, &chk->list);
6430
6431 if (mysql_req) {
6432 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006433 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006434 if (!chk) {
6435 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6436 goto error;
6437 }
6438 chk->expect.custom = tcpcheck_mysql_expect_ok;
6439 chk->index = index++;
6440 LIST_ADDQ(&rs->rules, &chk->list);
6441 }
6442
6443 LIST_ADDQ(&tcpchecks_list, &rs->list);
6444
6445 ruleset_found:
6446 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006447 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
Christopher Fauletf2b3be52020-04-02 18:07:37 +02006448
6449 out:
6450 free(errmsg);
6451 return err_code;
6452
6453 error:
6454 free(hdr);
Christopher Fauletce355072020-04-02 11:44:39 +02006455 free(user);
6456 free(var);
6457 free_tcpcheck_vars(&rules->preset_vars);
6458 tcpcheck_ruleset_release(rs);
6459 err_code |= ERR_ALERT | ERR_FATAL;
6460 goto out;
6461}
6462
Christopher Faulet1997eca2020-04-03 23:13:50 +02006463int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6464 const char *file, int line)
6465{
6466 static char *ldap_req = "300C020101600702010304008000";
6467
6468 struct tcpcheck_ruleset *rs = NULL;
6469 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6470 struct tcpcheck_rule *chk;
6471 char *errmsg = NULL;
6472 int err_code = 0;
6473
6474 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6475 err_code |= ERR_WARN;
6476
6477 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6478 goto out;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006479
6480 curpx->options2 &= ~PR_O2_CHK_ANY;
6481 curpx->options2 |= PR_O2_TCPCHK_CHK;
6482
6483 free_tcpcheck_vars(&rules->preset_vars);
6484 rules->list = NULL;
6485 rules->flags = 0;
6486
6487 rs = tcpcheck_ruleset_lookup("*ldap-check");
6488 if (rs)
6489 goto ruleset_found;
6490
6491 rs = tcpcheck_ruleset_create("*ldap-check");
6492 if (rs == NULL) {
6493 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6494 goto error;
6495 }
6496
6497 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
6498 1, curpx, &rs->rules, file, line, &errmsg);
6499 if (!chk) {
6500 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6501 goto error;
6502 }
6503 chk->index = 0;
6504 LIST_ADDQ(&rs->rules, &chk->list);
6505
6506 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
6507 "min-recv", "14",
6508 "on-error", "Not LDAPv3 protocol",
6509 ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006510 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006511 if (!chk) {
6512 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6513 goto error;
6514 }
6515 chk->index = 1;
6516 LIST_ADDQ(&rs->rules, &chk->list);
6517
6518 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006519 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006520 if (!chk) {
6521 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6522 goto error;
6523 }
6524 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
6525 chk->index = 2;
6526 LIST_ADDQ(&rs->rules, &chk->list);
6527
6528 LIST_ADDQ(&tcpchecks_list, &rs->list);
6529
6530 ruleset_found:
6531 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006532 rules->flags |= TCPCHK_RULES_LDAP_CHK;
Christopher Faulet1997eca2020-04-03 23:13:50 +02006533
6534 out:
Christopher Faulet267b01b2020-04-04 10:27:09 +02006535 free(errmsg);
6536 return err_code;
6537
6538 error:
6539 tcpcheck_ruleset_release(rs);
6540 err_code |= ERR_ALERT | ERR_FATAL;
6541 goto out;
6542}
6543
6544int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6545 const char *file, int line)
6546{
6547 struct tcpcheck_ruleset *rs = NULL;
6548 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6549 struct tcpcheck_rule *chk;
6550 char *spop_req = NULL;
6551 char *errmsg = NULL;
6552 int spop_len = 0, err_code = 0;
6553
6554 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6555 err_code |= ERR_WARN;
6556
6557 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6558 goto out;
6559
Christopher Faulet267b01b2020-04-04 10:27:09 +02006560 curpx->options2 &= ~PR_O2_CHK_ANY;
6561 curpx->options2 |= PR_O2_TCPCHK_CHK;
6562
6563 free_tcpcheck_vars(&rules->preset_vars);
6564 rules->list = NULL;
6565 rules->flags = 0;
6566
6567
6568 rs = tcpcheck_ruleset_lookup("*spop-check");
6569 if (rs)
6570 goto ruleset_found;
6571
6572 rs = tcpcheck_ruleset_create("*spop-check");
6573 if (rs == NULL) {
6574 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6575 goto error;
6576 }
6577
6578 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
6579 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6580 goto error;
6581 }
6582 chunk_reset(&trash);
6583 dump_binary(&trash, spop_req, spop_len);
6584 trash.area[trash.data] = '\0';
6585
6586 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
6587 1, curpx, &rs->rules, file, line, &errmsg);
6588 if (!chk) {
6589 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6590 goto error;
6591 }
6592 chk->index = 0;
6593 LIST_ADDQ(&rs->rules, &chk->list);
6594
6595 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006596 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
Christopher Faulet267b01b2020-04-04 10:27:09 +02006597 if (!chk) {
6598 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
6599 goto error;
6600 }
6601 chk->expect.custom = tcpcheck_spop_expect_agenthello;
6602 chk->index = 1;
6603 LIST_ADDQ(&rs->rules, &chk->list);
6604
6605 LIST_ADDQ(&tcpchecks_list, &rs->list);
6606
6607 ruleset_found:
6608 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006609 rules->flags |= TCPCHK_RULES_SPOP_CHK;
Christopher Faulet267b01b2020-04-04 10:27:09 +02006610
6611 out:
6612 free(spop_req);
Christopher Faulet1997eca2020-04-03 23:13:50 +02006613 free(errmsg);
6614 return err_code;
6615
6616 error:
6617 tcpcheck_ruleset_release(rs);
6618 err_code |= ERR_ALERT | ERR_FATAL;
6619 goto out;
6620}
Christopher Fauletce355072020-04-02 11:44:39 +02006621
Christopher Faulete5870d82020-04-15 11:32:03 +02006622
6623struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
6624{
6625 struct tcpcheck_rule *chk = NULL;
6626 struct tcpcheck_http_hdr *hdr = NULL;
6627 char *meth = NULL, *uri = NULL, *vsn = NULL;
6628 char *hdrs, *body;
6629
6630 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
6631 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
6632 if (hdrs == body)
6633 hdrs = NULL;
6634 if (hdrs) {
6635 *hdrs = '\0';
6636 hdrs +=2;
6637 }
6638 if (body) {
6639 *body = '\0';
6640 body += 4;
6641 }
6642 if (hdrs || body) {
6643 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
6644 " Please, consider to use 'http-check send' directive instead.");
6645 }
6646
6647 chk = calloc(1, sizeof(*chk));
6648 if (!chk) {
6649 memprintf(errmsg, "out of memory");
6650 goto error;
6651 }
6652 chk->action = TCPCHK_ACT_SEND;
6653 chk->send.type = TCPCHK_SEND_HTTP;
6654 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
6655 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
6656 LIST_INIT(&chk->send.http.hdrs);
6657
6658 /* Copy the method, uri and version */
6659 if (*args[cur_arg]) {
6660 if (!*args[cur_arg+1])
6661 uri = args[cur_arg];
6662 else
6663 meth = args[cur_arg];
6664 }
6665 if (*args[cur_arg+1])
6666 uri = args[cur_arg+1];
6667 if (*args[cur_arg+2])
6668 vsn = args[cur_arg+2];
6669
6670 if (meth) {
6671 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
6672 chk->send.http.meth.str.area = strdup(meth);
6673 chk->send.http.meth.str.data = strlen(meth);
6674 if (!chk->send.http.meth.str.area) {
6675 memprintf(errmsg, "out of memory");
6676 goto error;
6677 }
6678 }
6679 if (uri) {
6680 chk->send.http.uri = ist2(strdup(uri), strlen(uri));
6681 if (!chk->send.http.uri.ptr) {
6682 memprintf(errmsg, "out of memory");
6683 goto error;
6684 }
6685 }
6686 if (vsn) {
6687 chk->send.http.vsn = ist2(strdup(vsn), strlen(vsn));
6688 if (!chk->send.http.vsn.ptr) {
6689 memprintf(errmsg, "out of memory");
6690 goto error;
6691 }
6692 }
6693
6694 /* Copy the header */
6695 if (hdrs) {
6696 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
6697 struct h1m h1m;
6698 int i, ret;
6699
6700 /* Build and parse the request */
6701 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
6702
6703 h1m.flags = H1_MF_HDRS_ONLY;
6704 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
6705 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
6706 &h1m, NULL);
6707 if (ret <= 0) {
6708 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
6709 goto error;
6710 }
6711
6712 for (i = 0; tmp_hdrs[i].n.len; i++) {
6713 hdr = calloc(1, sizeof(*hdr));
6714 if (!hdr) {
6715 memprintf(errmsg, "out of memory");
6716 goto error;
6717 }
6718 LIST_INIT(&hdr->value);
6719 hdr->name = ist2(strdup(tmp_hdrs[i].n.ptr), tmp_hdrs[i].n.len);
6720 if (!hdr->name.ptr) {
6721 memprintf(errmsg, "out of memory");
6722 goto error;
6723 }
6724
6725 tmp_hdrs[i].v.ptr[tmp_hdrs[i].v.len] = '\0';
6726 if (!parse_logformat_string(tmp_hdrs[i].v.ptr, px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
6727 goto error;
6728 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
6729 }
6730 }
6731
6732 /* Copy the body */
6733 if (body) {
6734 chk->send.http.body = ist2(strdup(body), strlen(body));
6735 if (!chk->send.http.body.ptr) {
6736 memprintf(errmsg, "out of memory");
6737 goto error;
6738 }
6739 }
6740
6741 return chk;
6742
6743 error:
6744 free_tcpcheck_http_hdr(hdr);
6745 free_tcpcheck(chk, 0);
6746 return NULL;
6747}
6748
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006749int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6750 const char *file, int line)
6751{
Christopher Faulete5870d82020-04-15 11:32:03 +02006752 struct tcpcheck_ruleset *rs = NULL;
6753 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
6754 struct tcpcheck_rule *chk;
6755 char *errmsg = NULL;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006756 int err_code = 0;
6757
6758 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
6759 err_code |= ERR_WARN;
6760
6761 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
6762 goto out;
6763
Christopher Faulete5870d82020-04-15 11:32:03 +02006764 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
6765 if (!chk) {
6766 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6767 goto error;
6768 }
6769 if (errmsg) {
6770 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
6771 err_code |= ERR_WARN;
6772 free(errmsg);
6773 errmsg = NULL;
6774 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006775
Christopher Faulete5870d82020-04-15 11:32:03 +02006776 no_request:
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006777 curpx->options2 &= ~PR_O2_CHK_ANY;
Christopher Faulete5870d82020-04-15 11:32:03 +02006778 curpx->options2 |= PR_O2_TCPCHK_CHK;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006779
Christopher Faulete5870d82020-04-15 11:32:03 +02006780 free_tcpcheck_vars(&rules->preset_vars);
6781 rules->list = NULL;
6782 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006783
Christopher Faulete5870d82020-04-15 11:32:03 +02006784 /* Deduce the ruleset name from the proxy info */
6785 chunk_printf(&trash, "*http-check-%s_%s-%d",
6786 ((curpx == defpx) ? "defaults" : curpx->id),
6787 curpx->conf.file, curpx->conf.line);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006788
Christopher Faulete5870d82020-04-15 11:32:03 +02006789 rs = tcpcheck_ruleset_lookup(b_orig(&trash));
6790 if (rs == NULL) {
6791 rs = tcpcheck_ruleset_create(b_orig(&trash));
6792 if (rs == NULL) {
6793 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
6794 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006795 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006796 }
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006797
Christopher Faulete5870d82020-04-15 11:32:03 +02006798 rules->list = &rs->rules;
6799 rules->flags |= TCPCHK_RULES_HTTP_CHK;
6800 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
6801 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
6802 rules->list = NULL;
6803 goto error;
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006804 }
Christopher Faulete5870d82020-04-15 11:32:03 +02006805
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006806 out:
Christopher Faulete5870d82020-04-15 11:32:03 +02006807 free(errmsg);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006808 return err_code;
6809
6810 error:
Christopher Faulete5870d82020-04-15 11:32:03 +02006811 tcpcheck_ruleset_release(rs);
6812 free_tcpcheck(chk, 0);
Christopher Faulet6c2a7432020-04-09 14:48:48 +02006813 err_code |= ERR_ALERT | ERR_FATAL;
6814 goto out;
6815}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006816
Christopher Faulet6f557912020-04-09 15:58:50 +02006817int proxy_parse_external_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
6818 const char *file, int line)
6819{
6820 int err_code = 0;
6821
Christopher Faulet6f557912020-04-09 15:58:50 +02006822 curpx->options2 &= ~PR_O2_CHK_ANY;
6823 curpx->options2 |= PR_O2_EXT_CHK;
6824 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
6825 goto out;
6826
6827 out:
6828 return err_code;
6829}
6830
Christopher Fauletce8111e2020-04-06 15:04:11 +02006831/* Parse the "addr" server keyword */
6832static int srv_parse_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6833 char **errmsg)
6834{
6835 struct sockaddr_storage *sk;
6836 struct protocol *proto;
6837 int port1, port2, err_code = 0;
6838
6839
6840 if (!*args[*cur_arg+1]) {
6841 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[*cur_arg]);
6842 goto error;
6843 }
6844
6845 sk = str2sa_range(args[*cur_arg+1], NULL, &port1, &port2, errmsg, NULL, NULL, 1);
6846 if (!sk) {
6847 memprintf(errmsg, "'%s' : %s", args[*cur_arg], *errmsg);
6848 goto error;
6849 }
6850
6851 proto = protocol_by_family(sk->ss_family);
6852 if (!proto || !proto->connect) {
6853 memprintf(errmsg, "'%s %s' : connect() not supported for this address family.",
6854 args[*cur_arg], args[*cur_arg+1]);
6855 goto error;
6856 }
6857
6858 if (port1 != port2) {
6859 memprintf(errmsg, "'%s' : port ranges and offsets are not allowed in '%s'.",
6860 args[*cur_arg], args[*cur_arg+1]);
6861 goto error;
6862 }
6863
6864 srv->check.addr = srv->agent.addr = *sk;
6865 srv->flags |= SRV_F_CHECKADDR;
6866 srv->flags |= SRV_F_AGENTADDR;
6867
6868 out:
6869 return err_code;
6870
6871 error:
6872 err_code |= ERR_ALERT | ERR_FATAL;
6873 goto out;
6874}
6875
6876
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006877/* Parse the "agent-addr" server keyword */
6878static int srv_parse_agent_addr(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6879 char **errmsg)
6880{
6881 int err_code = 0;
6882
6883 if (!*(args[*cur_arg+1])) {
6884 memprintf(errmsg, "'%s' expects an address as argument.", args[*cur_arg]);
6885 goto error;
6886 }
6887 if(str2ip(args[*cur_arg+1], &srv->agent.addr) == NULL) {
6888 memprintf(errmsg, "parsing agent-addr failed. Check if '%s' is correct address.", args[*cur_arg+1]);
6889 goto error;
6890 }
6891
6892 out:
6893 return err_code;
6894
6895 error:
6896 err_code |= ERR_ALERT | ERR_FATAL;
6897 goto out;
6898}
6899
6900/* Parse the "agent-check" server keyword */
6901static int srv_parse_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6902 char **errmsg)
6903{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006904 struct tcpcheck_ruleset *rs = NULL;
6905 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
6906 struct tcpcheck_rule *chk;
6907 int err_code = 0;
6908
6909 if (srv->do_agent)
6910 goto out;
6911
6912 if (!rules) {
6913 rules = calloc(1, sizeof(*rules));
6914 if (!rules) {
6915 memprintf(errmsg, "out of memory.");
6916 goto error;
6917 }
6918 LIST_INIT(&rules->preset_vars);
6919 srv->agent.tcpcheck_rules = rules;
6920 }
6921 rules->list = NULL;
6922 rules->flags = 0;
6923
6924 rs = tcpcheck_ruleset_lookup("*agent-check");
6925 if (rs)
6926 goto ruleset_found;
6927
6928 rs = tcpcheck_ruleset_create("*agent-check");
6929 if (rs == NULL) {
6930 memprintf(errmsg, "out of memory.");
6931 goto error;
6932 }
6933
6934 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "%[var(check.agent_string)]", "log-format", ""},
6935 1, curpx, &rs->rules, srv->conf.file, srv->conf.line, errmsg);
6936 if (!chk) {
6937 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6938 goto error;
6939 }
6940 chk->index = 0;
6941 LIST_ADDQ(&rs->rules, &chk->list);
6942
6943 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
Christopher Faulete5870d82020-04-15 11:32:03 +02006944 1, curpx, &rs->rules, TCPCHK_RULES_AGENT_CHK,
6945 srv->conf.file, srv->conf.line, errmsg);
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006946 if (!chk) {
6947 memprintf(errmsg, "'%s': %s", args[*cur_arg], *errmsg);
6948 goto error;
6949 }
6950 chk->expect.custom = tcpcheck_agent_expect_reply;
6951 chk->index = 1;
6952 LIST_ADDQ(&rs->rules, &chk->list);
6953
6954 LIST_ADDQ(&tcpchecks_list, &rs->list);
6955
6956 ruleset_found:
6957 rules->list = &rs->rules;
Christopher Faulet404f9192020-04-09 23:13:54 +02006958 rules->flags |= TCPCHK_RULES_AGENT_CHK;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006959 srv->do_agent = 1;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006960
6961 out:
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006962 return 0;
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02006963
6964 error:
6965 deinit_srv_agent_check(srv);
6966 tcpcheck_ruleset_release(rs);
6967 err_code |= ERR_ALERT | ERR_FATAL;
6968 goto out;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02006969}
6970
6971/* Parse the "agent-inter" server keyword */
6972static int srv_parse_agent_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
6973 char **errmsg)
6974{
6975 const char *err = NULL;
6976 unsigned int delay;
6977 int err_code = 0;
6978
6979 if (!*(args[*cur_arg+1])) {
6980 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
6981 goto error;
6982 }
6983
6984 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
6985 if (err == PARSE_TIME_OVER) {
6986 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
6987 args[*cur_arg+1], args[*cur_arg], srv->id);
6988 goto error;
6989 }
6990 else if (err == PARSE_TIME_UNDER) {
6991 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
6992 args[*cur_arg+1], args[*cur_arg], srv->id);
6993 goto error;
6994 }
6995 else if (err) {
6996 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
6997 *err, srv->id);
6998 goto error;
6999 }
7000 if (delay <= 0) {
7001 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7002 delay, args[*cur_arg], srv->id);
7003 goto error;
7004 }
7005 srv->agent.inter = delay;
7006
7007 out:
7008 return err_code;
7009
7010 error:
7011 err_code |= ERR_ALERT | ERR_FATAL;
7012 goto out;
7013}
7014
7015/* Parse the "agent-port" server keyword */
7016static int srv_parse_agent_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7017 char **errmsg)
7018{
7019 int err_code = 0;
7020
7021 if (!*(args[*cur_arg+1])) {
7022 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7023 goto error;
7024 }
7025
7026 global.maxsock++;
7027 srv->agent.port = atol(args[*cur_arg+1]);
7028
7029 out:
7030 return err_code;
7031
7032 error:
7033 err_code |= ERR_ALERT | ERR_FATAL;
7034 goto out;
7035}
7036
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007037int set_srv_agent_send(struct server *srv, const char *send)
7038{
7039 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
7040 struct tcpcheck_var *var = NULL;
7041 char *str;
7042
7043 str = strdup(send);
7044 var = tcpcheck_var_create("check.agent_string");
7045 if (str == NULL || var == NULL)
7046 goto error;
7047
7048 free_tcpcheck_vars(&rules->preset_vars);
7049
7050 var->data.type = SMP_T_STR;
7051 var->data.u.str.area = str;
7052 var->data.u.str.data = strlen(str);
7053 LIST_INIT(&var->list);
7054 LIST_ADDQ(&rules->preset_vars, &var->list);
7055
7056 return 1;
7057
7058 error:
7059 free(str);
7060 free(var);
7061 return 0;
7062}
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007063
7064/* Parse the "agent-send" server keyword */
7065static int srv_parse_agent_send(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7066 char **errmsg)
7067{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007068 struct tcpcheck_rules *rules = srv->agent.tcpcheck_rules;
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007069 int err_code = 0;
7070
7071 if (!*(args[*cur_arg+1])) {
7072 memprintf(errmsg, "'%s' expects a string as argument.", args[*cur_arg]);
7073 goto error;
7074 }
7075
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007076 if (!rules) {
7077 rules = calloc(1, sizeof(*rules));
7078 if (!rules) {
7079 memprintf(errmsg, "out of memory.");
7080 goto error;
7081 }
7082 LIST_INIT(&rules->preset_vars);
7083 srv->agent.tcpcheck_rules = rules;
7084 }
7085
7086 if (!set_srv_agent_send(srv, args[*cur_arg+1])) {
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007087 memprintf(errmsg, "out of memory.");
7088 goto error;
7089 }
7090
7091 out:
7092 return err_code;
7093
7094 error:
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007095 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007096 err_code |= ERR_ALERT | ERR_FATAL;
7097 goto out;
7098}
7099
7100/* Parse the "no-agent-send" server keyword */
7101static int srv_parse_no_agent_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7102 char **errmsg)
7103{
Christopher Faulet0ae3d1d2020-04-06 17:54:24 +02007104 deinit_srv_agent_check(srv);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007105 return 0;
7106}
7107
Christopher Fauletce8111e2020-04-06 15:04:11 +02007108/* Parse the "check" server keyword */
7109static int srv_parse_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7110 char **errmsg)
7111{
7112 srv->do_check = 1;
7113 return 0;
7114}
7115
7116/* Parse the "check-send-proxy" server keyword */
7117static int srv_parse_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7118 char **errmsg)
7119{
7120 srv->check.send_proxy = 1;
7121 return 0;
7122}
7123
7124/* Parse the "check-via-socks4" server keyword */
7125static int srv_parse_check_via_socks4(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7126 char **errmsg)
7127{
7128 srv->check.via_socks4 = 1;
7129 return 0;
7130}
7131
7132/* Parse the "no-check" server keyword */
7133static int srv_parse_no_check(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7134 char **errmsg)
7135{
7136 deinit_srv_check(srv);
7137 return 0;
7138}
7139
7140/* Parse the "no-check-send-proxy" server keyword */
7141static int srv_parse_no_check_send_proxy(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7142 char **errmsg)
7143{
7144 srv->check.send_proxy = 0;
7145 return 0;
7146}
7147
7148/* Parse the "rise" server keyword */
7149static int srv_parse_check_rise(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7150 char **errmsg)
7151{
7152 int err_code = 0;
7153
7154 if (!*args[*cur_arg + 1]) {
7155 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7156 goto error;
7157 }
7158
7159 srv->check.rise = atol(args[*cur_arg+1]);
7160 if (srv->check.rise <= 0) {
7161 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7162 goto error;
7163 }
7164
7165 if (srv->check.health)
7166 srv->check.health = srv->check.rise;
7167
7168 out:
7169 return err_code;
7170
7171 error:
7172 deinit_srv_agent_check(srv);
7173 err_code |= ERR_ALERT | ERR_FATAL;
7174 goto out;
7175 return 0;
7176}
7177
7178/* Parse the "fall" server keyword */
7179static int srv_parse_check_fall(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7180 char **errmsg)
7181{
7182 int err_code = 0;
7183
7184 if (!*args[*cur_arg + 1]) {
7185 memprintf(errmsg, "'%s' expects an integer argument.", args[*cur_arg]);
7186 goto error;
7187 }
7188
7189 srv->check.fall = atol(args[*cur_arg+1]);
7190 if (srv->check.fall <= 0) {
7191 memprintf(errmsg, "'%s' has to be > 0.", args[*cur_arg]);
7192 goto error;
7193 }
7194
7195 out:
7196 return err_code;
7197
7198 error:
7199 deinit_srv_agent_check(srv);
7200 err_code |= ERR_ALERT | ERR_FATAL;
7201 goto out;
7202 return 0;
7203}
7204
7205/* Parse the "inter" server keyword */
7206static int srv_parse_check_inter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7207 char **errmsg)
7208{
7209 const char *err = NULL;
7210 unsigned int delay;
7211 int err_code = 0;
7212
7213 if (!*(args[*cur_arg+1])) {
7214 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7215 goto error;
7216 }
7217
7218 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7219 if (err == PARSE_TIME_OVER) {
7220 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7221 args[*cur_arg+1], args[*cur_arg], srv->id);
7222 goto error;
7223 }
7224 else if (err == PARSE_TIME_UNDER) {
7225 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7226 args[*cur_arg+1], args[*cur_arg], srv->id);
7227 goto error;
7228 }
7229 else if (err) {
7230 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7231 *err, srv->id);
7232 goto error;
7233 }
7234 if (delay <= 0) {
7235 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7236 delay, args[*cur_arg], srv->id);
7237 goto error;
7238 }
7239 srv->check.inter = delay;
7240
7241 out:
7242 return err_code;
7243
7244 error:
7245 err_code |= ERR_ALERT | ERR_FATAL;
7246 goto out;
7247}
7248
7249
7250/* Parse the "fastinter" server keyword */
7251static int srv_parse_check_fastinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7252 char **errmsg)
7253{
7254 const char *err = NULL;
7255 unsigned int delay;
7256 int err_code = 0;
7257
7258 if (!*(args[*cur_arg+1])) {
7259 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7260 goto error;
7261 }
7262
7263 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7264 if (err == PARSE_TIME_OVER) {
7265 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7266 args[*cur_arg+1], args[*cur_arg], srv->id);
7267 goto error;
7268 }
7269 else if (err == PARSE_TIME_UNDER) {
7270 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7271 args[*cur_arg+1], args[*cur_arg], srv->id);
7272 goto error;
7273 }
7274 else if (err) {
7275 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7276 *err, srv->id);
7277 goto error;
7278 }
7279 if (delay <= 0) {
7280 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7281 delay, args[*cur_arg], srv->id);
7282 goto error;
7283 }
7284 srv->check.fastinter = delay;
7285
7286 out:
7287 return err_code;
7288
7289 error:
7290 err_code |= ERR_ALERT | ERR_FATAL;
7291 goto out;
7292}
7293
7294
7295/* Parse the "downinter" server keyword */
7296static int srv_parse_check_downinter(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7297 char **errmsg)
7298{
7299 const char *err = NULL;
7300 unsigned int delay;
7301 int err_code = 0;
7302
7303 if (!*(args[*cur_arg+1])) {
7304 memprintf(errmsg, "'%s' expects a delay as argument.", args[*cur_arg]);
7305 goto error;
7306 }
7307
7308 err = parse_time_err(args[*cur_arg+1], &delay, TIME_UNIT_MS);
7309 if (err == PARSE_TIME_OVER) {
7310 memprintf(errmsg, "timer overflow in argument <%s> to <%s> of server %s, maximum value is 2147483647 ms (~24.8 days).",
7311 args[*cur_arg+1], args[*cur_arg], srv->id);
7312 goto error;
7313 }
7314 else if (err == PARSE_TIME_UNDER) {
7315 memprintf(errmsg, "timer underflow in argument <%s> to <%s> of server %s, minimum non-null value is 1 ms.",
7316 args[*cur_arg+1], args[*cur_arg], srv->id);
7317 goto error;
7318 }
7319 else if (err) {
7320 memprintf(errmsg, "unexpected character '%c' in 'agent-inter' argument of server %s.",
7321 *err, srv->id);
7322 goto error;
7323 }
7324 if (delay <= 0) {
7325 memprintf(errmsg, "invalid value %d for argument '%s' of server %s.",
7326 delay, args[*cur_arg], srv->id);
7327 goto error;
7328 }
7329 srv->check.downinter = delay;
7330
7331 out:
7332 return err_code;
7333
7334 error:
7335 err_code |= ERR_ALERT | ERR_FATAL;
7336 goto out;
7337}
7338
7339/* Parse the "port" server keyword */
7340static int srv_parse_check_port(char **args, int *cur_arg, struct proxy *curpx, struct server *srv,
7341 char **errmsg)
7342{
7343 int err_code = 0;
7344
7345 if (!*(args[*cur_arg+1])) {
7346 memprintf(errmsg, "'%s' expects a port number as argument.", args[*cur_arg]);
7347 goto error;
7348 }
7349
7350 global.maxsock++;
7351 srv->check.port = atol(args[*cur_arg+1]);
7352 srv->flags |= SRV_F_CHECKPORT;
7353
7354 out:
7355 return err_code;
7356
7357 error:
7358 err_code |= ERR_ALERT | ERR_FATAL;
7359 goto out;
7360}
7361
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007362static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulete9111b62020-04-09 18:12:08 +02007363 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
7364 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
7365 { CFG_LISTEN, "external-check", proxy_parse_extcheck },
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007366 { 0, NULL, NULL },
7367}};
7368
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007369static struct srv_kw_list srv_kws = { "CHK", { }, {
Christopher Fauletce8111e2020-04-06 15:04:11 +02007370 { "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 +02007371 { "agent-addr", srv_parse_agent_addr, 1, 1 }, /* Enable an auxiliary agent check */
7372 { "agent-check", srv_parse_agent_check, 0, 1 }, /* Enable agent checks */
7373 { "agent-inter", srv_parse_agent_inter, 1, 1 }, /* Set the interval between two agent checks */
7374 { "agent-port", srv_parse_agent_port, 1, 1 }, /* Set the TCP port used for agent checks. */
7375 { "agent-send", srv_parse_agent_send, 1, 1 }, /* Set string to send to agent. */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007376 { "check", srv_parse_check, 0, 1 }, /* Enable health checks */
7377 { "check-send-proxy", srv_parse_check_send_proxy, 0, 1 }, /* Enable PROXY protocol for health checks */
7378 { "check-via-socks4", srv_parse_check_via_socks4, 0, 1 }, /* Enable socks4 proxy for health checks */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007379 { "no-agent-check", srv_parse_no_agent_check, 0, 1 }, /* Do not enable any auxiliary agent check */
Christopher Fauletce8111e2020-04-06 15:04:11 +02007380 { "no-check", srv_parse_no_check, 0, 1 }, /* Disable health checks */
7381 { "no-check-send-proxy", srv_parse_no_check_send_proxy, 0, 1 }, /* Disable PROXY protol for health checks */
7382 { "rise", srv_parse_check_rise, 1, 1 }, /* Set rise value for health checks */
7383 { "fall", srv_parse_check_fall, 1, 1 }, /* Set fall value for health checks */
7384 { "inter", srv_parse_check_inter, 1, 1 }, /* Set inter value for health checks */
7385 { "fastinter", srv_parse_check_fastinter, 1, 1 }, /* Set fastinter value for health checks */
7386 { "downinter", srv_parse_check_downinter, 1, 1 }, /* Set downinter value for health checks */
7387 { "port", srv_parse_check_port, 1, 1 }, /* Set the TCP port used for health checks. */
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007388 { NULL, NULL, 0 },
7389}};
7390
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007391INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
Christopher Fauletcbba66c2020-04-06 14:26:30 +02007392INITCALL1(STG_REGISTER, srv_register_keywords, &srv_kws);
Christopher Fauletfd6c2292020-03-25 18:20:15 +01007393
Willy Tarreaubd741542010-03-16 18:46:54 +01007394/*
Willy Tarreaubaaee002006-06-26 02:48:02 +02007395 * Local variables:
7396 * c-indent-level: 8
7397 * c-basic-offset: 8
7398 * End:
7399 */