blob: 3b1d75a18f0aaa00b218a2ff525b7853541f2b69 [file] [log] [blame]
Willy Tarreau51cd5952020-06-05 12:25:38 +02001/*
2 * Health-checks functions.
3 *
4 * Copyright 2000-2009,2020 Willy Tarreau <w@1wt.eu>
5 * Copyright 2007-2010 Krzysztof Piotr Oledzki <ole@ans.pl>
6 * Copyright 2013 Baptiste Assmann <bedis9@gmail.com>
7 * Copyright 2020 Gaetan Rivet <grive@u256.net>
8 * Copyright 2020 Christopher Faulet <cfaulet@haproxy.com>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version
13 * 2 of the License, or (at your option) any later version.
14 *
15 */
16
17#include <sys/resource.h>
18#include <sys/socket.h>
19#include <sys/types.h>
20#include <sys/wait.h>
21#include <netinet/in.h>
22#include <netinet/tcp.h>
23#include <arpa/inet.h>
24
25#include <ctype.h>
26#include <errno.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020027#include <signal.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <time.h>
33#include <unistd.h>
34
35#include <haproxy/action.h>
36#include <haproxy/api.h>
37#include <haproxy/cfgparse.h>
38#include <haproxy/check.h>
39#include <haproxy/chunk.h>
40#include <haproxy/connection.h>
Christopher Faulet1329f2a2021-12-16 17:32:56 +010041#include <haproxy/conn_stream.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020042#include <haproxy/errors.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020043#include <haproxy/global.h>
44#include <haproxy/h1.h>
45#include <haproxy/http.h>
46#include <haproxy/http_htx.h>
47#include <haproxy/htx.h>
48#include <haproxy/istbuf.h>
49#include <haproxy/list.h>
50#include <haproxy/log.h>
Christopher Faulet8a0e5f82021-09-16 16:01:09 +020051#include <haproxy/net_helper.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020052#include <haproxy/protocol.h>
53#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020054#include <haproxy/regex.h>
55#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020056#include <haproxy/server.h>
57#include <haproxy/ssl_sock.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020058#include <haproxy/task.h>
59#include <haproxy/tcpcheck.h>
Willy Tarreau9310f482021-10-06 16:18:40 +020060#include <haproxy/ticks.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020061#include <haproxy/tools.h>
Christopher Faulet147b8c92021-04-10 09:00:38 +020062#include <haproxy/trace.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020063#include <haproxy/vars.h>
64
65
Christopher Faulet147b8c92021-04-10 09:00:38 +020066#define TRACE_SOURCE &trace_check
67
Willy Tarreau51cd5952020-06-05 12:25:38 +020068/* Global tree to share all tcp-checks */
69struct eb_root shared_tcpchecks = EB_ROOT;
70
71
72DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
73
74/**************************************************************************/
75/*************** Init/deinit tcp-check rules and ruleset ******************/
76/**************************************************************************/
77/* Releases memory allocated for a log-format string */
78static void free_tcpcheck_fmt(struct list *fmt)
79{
80 struct logformat_node *lf, *lfb;
81
82 list_for_each_entry_safe(lf, lfb, fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +020083 LIST_DELETE(&lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +020084 release_sample_expr(lf->expr);
85 free(lf->arg);
86 free(lf);
87 }
88}
89
90/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
91void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
92{
93 if (!hdr)
94 return;
95
96 free_tcpcheck_fmt(&hdr->value);
97 istfree(&hdr->name);
98 free(hdr);
99}
100
101/* Releases memory allocated for an HTTP header list used in a tcp-check send
102 * rule
103 */
104static void free_tcpcheck_http_hdrs(struct list *hdrs)
105{
106 struct tcpcheck_http_hdr *hdr, *bhdr;
107
108 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200109 LIST_DELETE(&hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200110 free_tcpcheck_http_hdr(hdr);
111 }
112}
113
114/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
115 * tcp-check was allocated using a memory pool (it is used to instantiate email
116 * alerts).
117 */
118void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
119{
120 if (!rule)
121 return;
122
123 free(rule->comment);
124 switch (rule->action) {
125 case TCPCHK_ACT_SEND:
126 switch (rule->send.type) {
127 case TCPCHK_SEND_STRING:
128 case TCPCHK_SEND_BINARY:
129 istfree(&rule->send.data);
130 break;
131 case TCPCHK_SEND_STRING_LF:
132 case TCPCHK_SEND_BINARY_LF:
133 free_tcpcheck_fmt(&rule->send.fmt);
134 break;
135 case TCPCHK_SEND_HTTP:
136 free(rule->send.http.meth.str.area);
137 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
138 istfree(&rule->send.http.uri);
139 else
140 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
141 istfree(&rule->send.http.vsn);
142 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
143 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
144 istfree(&rule->send.http.body);
145 else
146 free_tcpcheck_fmt(&rule->send.http.body_fmt);
147 break;
148 case TCPCHK_SEND_UNDEF:
149 break;
150 }
151 break;
152 case TCPCHK_ACT_EXPECT:
153 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
154 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
155 release_sample_expr(rule->expect.status_expr);
156 switch (rule->expect.type) {
157 case TCPCHK_EXPECT_HTTP_STATUS:
158 free(rule->expect.codes.codes);
159 break;
160 case TCPCHK_EXPECT_STRING:
161 case TCPCHK_EXPECT_BINARY:
162 case TCPCHK_EXPECT_HTTP_BODY:
163 istfree(&rule->expect.data);
164 break;
165 case TCPCHK_EXPECT_STRING_REGEX:
166 case TCPCHK_EXPECT_BINARY_REGEX:
167 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
168 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
169 regex_free(rule->expect.regex);
170 break;
171 case TCPCHK_EXPECT_STRING_LF:
172 case TCPCHK_EXPECT_BINARY_LF:
173 case TCPCHK_EXPECT_HTTP_BODY_LF:
174 free_tcpcheck_fmt(&rule->expect.fmt);
175 break;
176 case TCPCHK_EXPECT_HTTP_HEADER:
177 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
178 regex_free(rule->expect.hdr.name_re);
179 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
180 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
181 else
182 istfree(&rule->expect.hdr.name);
183
184 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
185 regex_free(rule->expect.hdr.value_re);
186 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
187 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
188 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
189 istfree(&rule->expect.hdr.value);
190 break;
191 case TCPCHK_EXPECT_CUSTOM:
192 case TCPCHK_EXPECT_UNDEF:
193 break;
194 }
195 break;
196 case TCPCHK_ACT_CONNECT:
197 free(rule->connect.sni);
198 free(rule->connect.alpn);
199 release_sample_expr(rule->connect.port_expr);
200 break;
201 case TCPCHK_ACT_COMMENT:
202 break;
203 case TCPCHK_ACT_ACTION_KW:
204 free(rule->action_kw.rule);
205 break;
206 }
207
208 if (in_pool)
209 pool_free(pool_head_tcpcheck_rule, rule);
210 else
211 free(rule);
212}
213
214/* Creates a tcp-check variable used in preset variables before executing a
215 * tcp-check ruleset.
216 */
217struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
218{
219 struct tcpcheck_var *var = NULL;
220
221 var = calloc(1, sizeof(*var));
222 if (var == NULL)
223 return NULL;
224
225 var->name = istdup(name);
226 if (!isttest(var->name)) {
227 free(var);
228 return NULL;
229 }
230
231 LIST_INIT(&var->list);
232 return var;
233}
234
235/* Releases memory allocated for a preset tcp-check variable */
236void free_tcpcheck_var(struct tcpcheck_var *var)
237{
238 if (!var)
239 return;
240
241 istfree(&var->name);
242 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
243 free(var->data.u.str.area);
244 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
245 free(var->data.u.meth.str.area);
246 free(var);
247}
248
249/* Releases a list of preset tcp-check variables */
250void free_tcpcheck_vars(struct list *vars)
251{
252 struct tcpcheck_var *var, *back;
253
254 list_for_each_entry_safe(var, back, vars, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200255 LIST_DELETE(&var->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200256 free_tcpcheck_var(var);
257 }
258}
259
260/* Duplicate a list of preset tcp-check variables */
Willy Tarreau09f2e772021-02-12 08:42:30 +0100261int dup_tcpcheck_vars(struct list *dst, const struct list *src)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200262{
Willy Tarreau09f2e772021-02-12 08:42:30 +0100263 const struct tcpcheck_var *var;
264 struct tcpcheck_var *new = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200265
266 list_for_each_entry(var, src, list) {
267 new = create_tcpcheck_var(var->name);
268 if (!new)
269 goto error;
270 new->data.type = var->data.type;
271 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
272 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
273 goto error;
274 if (var->data.type == SMP_T_STR)
275 new->data.u.str.area[new->data.u.str.data] = 0;
276 }
277 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
278 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
279 goto error;
280 new->data.u.str.area[new->data.u.str.data] = 0;
281 new->data.u.meth.meth = var->data.u.meth.meth;
282 }
283 else
284 new->data.u = var->data.u;
Willy Tarreau2b718102021-04-21 07:32:39 +0200285 LIST_APPEND(dst, &new->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200286 }
287 return 1;
288
289 error:
290 free(new);
291 return 0;
292}
293
294/* Looks for a shared tcp-check ruleset given its name. */
295struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
296{
297 struct tcpcheck_ruleset *rs;
298 struct ebpt_node *node;
299
300 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
301 if (node) {
302 rs = container_of(node, typeof(*rs), node);
303 return rs;
304 }
305 return NULL;
306}
307
308/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks
309 * tree.
310 */
311struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
312{
313 struct tcpcheck_ruleset *rs;
314
315 rs = calloc(1, sizeof(*rs));
316 if (rs == NULL)
317 return NULL;
318
319 rs->node.key = strdup(name);
320 if (rs->node.key == NULL) {
321 free(rs);
322 return NULL;
323 }
324
325 LIST_INIT(&rs->rules);
326 ebis_insert(&shared_tcpchecks, &rs->node);
327 return rs;
328}
329
330/* Releases memory allocated by a tcp-check ruleset. */
331void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
332{
333 struct tcpcheck_rule *r, *rb;
334
335 if (!rs)
336 return;
337
338 ebpt_delete(&rs->node);
339 free(rs->node.key);
340 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200341 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200342 free_tcpcheck(r, 0);
343 }
344 free(rs);
345}
346
347
348/**************************************************************************/
349/**************** Everything about tcp-checks execution *******************/
350/**************************************************************************/
351/* Returns the id of a step in a tcp-check ruleset */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200352int tcpcheck_get_step_id(const struct check *check, const struct tcpcheck_rule *rule)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200353{
354 if (!rule)
355 rule = check->current_step;
356
357 /* no last started step => first step */
358 if (!rule)
359 return 1;
360
361 /* last step is the first implicit connect */
362 if (rule->index == 0 &&
363 rule->action == TCPCHK_ACT_CONNECT &&
364 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
365 return 0;
366
367 return rule->index + 1;
368}
369
370/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
371 * NULL if none was found.
372 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200373struct tcpcheck_rule *get_first_tcpcheck_rule(const struct tcpcheck_rules *rules)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200374{
375 struct tcpcheck_rule *r;
376
377 list_for_each_entry(r, rules->list, list) {
378 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
379 return r;
380 }
381 return NULL;
382}
383
384/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
385 * NULL if none was found.
386 */
387static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
388{
389 struct tcpcheck_rule *r;
390
391 list_for_each_entry_rev(r, rules->list, list) {
392 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
393 return r;
394 }
395 return NULL;
396}
397
398/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
399 * <start> or NULL if non was found. If <start> is NULL, it relies on
400 * get_first_tcpcheck_rule().
401 */
402static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
403{
404 struct tcpcheck_rule *r;
405
406 if (!start)
407 return get_first_tcpcheck_rule(rules);
408
409 r = LIST_NEXT(&start->list, typeof(r), list);
410 list_for_each_entry_from(r, rules->list, list) {
411 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
412 return r;
413 }
414 return NULL;
415}
416
417
418/* Creates info message when a tcp-check healthcheck fails on an expect rule */
419static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
420 int match, struct ist info)
421{
422 struct sample *smp;
423
424 /* Follows these step to produce the info message:
425 * 1. if info field is already provided, copy it
426 * 2. if the expect rule provides an onerror log-format string,
427 * use it to produce the message
428 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
429 * 4. Otherwise produce the generic tcp-check info message
430 */
431 if (istlen(info)) {
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100432 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200433 goto comment;
434 }
435 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
436 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
437 goto comment;
438 }
439
440 if (check->type == PR_O2_TCPCHK_CHK &&
441 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
442 goto comment;
443
444 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
445 switch (rule->expect.type) {
446 case TCPCHK_EXPECT_HTTP_STATUS:
447 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
448 break;
449 case TCPCHK_EXPECT_STRING:
450 case TCPCHK_EXPECT_HTTP_BODY:
451 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
452 tcpcheck_get_step_id(check, rule));
453 break;
454 case TCPCHK_EXPECT_BINARY:
455 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
456 break;
457 case TCPCHK_EXPECT_STRING_REGEX:
458 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
459 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
460 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
461 break;
462 case TCPCHK_EXPECT_BINARY_REGEX:
463 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
464 break;
465 case TCPCHK_EXPECT_STRING_LF:
466 case TCPCHK_EXPECT_HTTP_BODY_LF:
467 chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule));
468 break;
469 case TCPCHK_EXPECT_BINARY_LF:
470 chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule));
471 break;
472 case TCPCHK_EXPECT_CUSTOM:
473 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
474 break;
475 case TCPCHK_EXPECT_HTTP_HEADER:
476 chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
477 case TCPCHK_EXPECT_UNDEF:
478 /* Should never happen. */
479 return;
480 }
481
482 comment:
483 /* If the failing expect rule provides a comment, it is concatenated to
484 * the info message.
485 */
486 if (rule->comment) {
487 chunk_strcat(msg, " comment: ");
488 chunk_strcat(msg, rule->comment);
489 }
490
491 /* Finally, the check status code is set if the failing expect rule
492 * defines a status expression.
493 */
494 if (rule->expect.status_expr) {
495 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
496 rule->expect.status_expr, SMP_T_STR);
497
498 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
499 sample_casts[smp->data.type][SMP_T_SINT](smp))
500 check->code = smp->data.u.sint;
501 }
502
503 *(b_tail(msg)) = '\0';
504}
505
506/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
507static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
508 struct ist info)
509{
510 struct sample *smp;
511
512 /* Follows these step to produce the info message:
513 * 1. if info field is already provided, copy it
514 * 2. if the expect rule provides an onsucces log-format string,
515 * use it to produce the message
516 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
517 * 4. Otherwise produce the generic tcp-check info message
518 */
519 if (istlen(info))
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100520 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200521 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
522 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
523 &rule->expect.onsuccess_fmt);
524 else if (check->type == PR_O2_TCPCHK_CHK &&
525 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
526 chunk_strcat(msg, "(tcp-check)");
527
528 /* Finally, the check status code is set if the expect rule defines a
529 * status expression.
530 */
531 if (rule->expect.status_expr) {
532 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
533 rule->expect.status_expr, SMP_T_STR);
534
535 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
536 sample_casts[smp->data.type][SMP_T_SINT](smp))
537 check->code = smp->data.u.sint;
538 }
539
540 *(b_tail(msg)) = '\0';
541}
542
543/* Internal functions to parse and validate a MySQL packet in the context of an
544 * expect rule. It start to parse the input buffer at the offset <offset>. If
545 * <last_read> is set, no more data are expected.
546 */
547static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
548 unsigned int offset, int last_read)
549{
550 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
551 enum healthcheck_status status;
552 struct buffer *msg = NULL;
553 struct ist desc = IST_NULL;
554 unsigned int err = 0, plen = 0;
555
556
Christopher Faulet147b8c92021-04-10 09:00:38 +0200557 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
558
Willy Tarreau51cd5952020-06-05 12:25:38 +0200559 /* 3 Bytes for the packet length and 1 byte for the sequence id */
560 if (b_data(&check->bi) < offset+4) {
561 if (!last_read)
562 goto wait_more_data;
563
564 /* invalid length or truncated response */
565 status = HCHK_STATUS_L7RSP;
566 goto error;
567 }
568
569 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
570 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
571 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
572
573 if (b_data(&check->bi) < offset+plen+4) {
574 if (!last_read)
575 goto wait_more_data;
576
577 /* invalid length or truncated response */
578 status = HCHK_STATUS_L7RSP;
579 goto error;
580 }
581
582 if (*b_peek(&check->bi, offset+4) == '\xff') {
583 /* MySQL Error packet always begin with field_count = 0xff */
584 status = HCHK_STATUS_L7STS;
585 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
586 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
587 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
588 goto error;
589 }
590
591 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
592 /* Not the last rule, continue */
593 goto out;
594 }
595
596 /* We set the MySQL Version in description for information purpose
597 * FIXME : it can be cool to use MySQL Version for other purpose,
598 * like mark as down old MySQL server.
599 */
600 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
601 set_server_check_status(check, status, b_peek(&check->bi, 5));
602
603 out:
604 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200605 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200606 return ret;
607
608 error:
609 ret = TCPCHK_EVAL_STOP;
610 check->code = err;
611 msg = alloc_trash_chunk();
612 if (msg)
613 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
614 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
615 goto out;
616
617 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200618 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200619 ret = TCPCHK_EVAL_WAIT;
620 goto out;
621}
622
623/* Custom tcp-check expect function to parse and validate the MySQL initial
624 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
625 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
626 * error occurred.
627 */
628enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
629{
630 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
631}
632
633/* Custom tcp-check expect function to parse and validate the MySQL OK packet
634 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
635 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
636 * an error occurred.
637 */
638enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
639{
640 unsigned int hslen = 0;
641
642 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
643 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
644 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
645
646 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
647}
648
649/* Custom tcp-check expect function to parse and validate the LDAP bind response
650 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
651 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
652 * error occurred.
653 */
654enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
655{
656 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
657 enum healthcheck_status status;
658 struct buffer *msg = NULL;
659 struct ist desc = IST_NULL;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200660 char *ptr;
661 unsigned short nbytes = 0;
662 size_t msglen = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200663
Christopher Faulet147b8c92021-04-10 09:00:38 +0200664 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
665
Willy Tarreau51cd5952020-06-05 12:25:38 +0200666 /* Check if the server speaks LDAP (ASN.1/BER)
667 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
668 * http://tools.ietf.org/html/rfc4511
669 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200670 ptr = b_head(&check->bi) + 1;
671
Willy Tarreau51cd5952020-06-05 12:25:38 +0200672 /* size of LDAPMessage */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200673 if (*ptr & 0x80) {
674 /* For message size encoded on several bytes, we only handle
675 * size encoded on 2 or 4 bytes. There is no reason to make this
676 * part to complex because only Active Directory is known to
677 * encode BindReponse length on 4 bytes.
678 */
679 nbytes = (*ptr & 0x7f);
680 if (b_data(&check->bi) < 1 + nbytes)
681 goto too_short;
682 switch (nbytes) {
683 case 4: msglen = read_n32(ptr+1); break;
684 case 2: msglen = read_n16(ptr+1); break;
685 default:
686 status = HCHK_STATUS_L7RSP;
687 desc = ist("Not LDAPv3 protocol");
688 goto error;
689 }
690 }
691 else
692 msglen = *ptr;
693 ptr += 1 + nbytes;
694
695 if (b_data(&check->bi) < 2 + nbytes + msglen)
696 goto too_short;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200697
698 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
699 * messageID: 0x02 0x01 0x01: INTEGER 1
700 * protocolOp: 0x61: bindResponse
701 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200702 if (memcmp(ptr, "\x02\x01\x01\x61", 4) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200703 status = HCHK_STATUS_L7RSP;
704 desc = ist("Not LDAPv3 protocol");
705 goto error;
706 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200707 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200708
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200709 /* skip size of bindResponse */
710 nbytes = 0;
711 if (*ptr & 0x80)
712 nbytes = (*ptr & 0x7f);
713 ptr += 1 + nbytes;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200714
715 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
716 * ldapResult: 0x0a 0x01: ENUMERATION
717 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200718 if (memcmp(ptr, "\x0a\x01", 2) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200719 status = HCHK_STATUS_L7RSP;
720 desc = ist("Not LDAPv3 protocol");
721 goto error;
722 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200723 ptr += 2;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200724
725 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
726 * resultCode
727 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200728 check->code = *ptr;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200729 if (check->code) {
730 status = HCHK_STATUS_L7STS;
731 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
732 goto error;
733 }
734
735 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
736 set_server_check_status(check, status, "Success");
737
738 out:
739 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200740 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200741 return ret;
742
743 error:
744 ret = TCPCHK_EVAL_STOP;
745 msg = alloc_trash_chunk();
746 if (msg)
747 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
748 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
749 goto out;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200750
751 too_short:
752 if (!last_read)
753 goto wait_more_data;
754 /* invalid length or truncated response */
755 status = HCHK_STATUS_L7RSP;
756 goto error;
757
758 wait_more_data:
759 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
760 ret = TCPCHK_EVAL_WAIT;
761 goto out;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200762}
763
764/* Custom tcp-check expect function to parse and validate the SPOP hello agent
765 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
766 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
767 */
768enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
769{
770 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
771 enum healthcheck_status status;
772 struct buffer *msg = NULL;
773 struct ist desc = IST_NULL;
774 unsigned int framesz;
775
Christopher Faulet147b8c92021-04-10 09:00:38 +0200776 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200777
778 memcpy(&framesz, b_head(&check->bi), 4);
779 framesz = ntohl(framesz);
780
781 if (!last_read && b_data(&check->bi) < (4+framesz))
782 goto wait_more_data;
783
784 memset(b_orig(&trash), 0, b_size(&trash));
785 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
786 status = HCHK_STATUS_L7RSP;
787 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
788 goto error;
789 }
790
791 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
792 set_server_check_status(check, status, "SPOA server is ok");
793
794 out:
795 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200796 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200797 return ret;
798
799 error:
800 ret = TCPCHK_EVAL_STOP;
801 msg = alloc_trash_chunk();
802 if (msg)
803 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
804 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
805 goto out;
806
807 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200808 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200809 ret = TCPCHK_EVAL_WAIT;
810 goto out;
811}
812
813/* Custom tcp-check expect function to parse and validate the agent-check
814 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
815 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
816 */
817enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
818{
819 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
820 enum healthcheck_status status = HCHK_STATUS_CHECKED;
821 const char *hs = NULL; /* health status */
822 const char *as = NULL; /* admin status */
823 const char *ps = NULL; /* performance status */
824 const char *cs = NULL; /* maxconn */
825 const char *err = NULL; /* first error to report */
826 const char *wrn = NULL; /* first warning to report */
827 char *cmd, *p;
828
Christopher Faulet147b8c92021-04-10 09:00:38 +0200829 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
830
Willy Tarreau51cd5952020-06-05 12:25:38 +0200831 /* We're getting an agent check response. The agent could
832 * have been disabled in the mean time with a long check
833 * still pending. It is important that we ignore the whole
834 * response.
835 */
836 if (!(check->state & CHK_ST_ENABLED))
837 goto out;
838
839 /* The agent supports strings made of a single line ended by the
840 * first CR ('\r') or LF ('\n'). This line is composed of words
841 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
842 * line may optionally contained a description of a state change
843 * after a sharp ('#'), which is only considered if a health state
844 * is announced.
845 *
846 * Words may be composed of :
847 * - a numeric weight suffixed by the percent character ('%').
848 * - a health status among "up", "down", "stopped", and "fail".
849 * - an admin status among "ready", "drain", "maint".
850 *
851 * These words may appear in any order. If multiple words of the
852 * same category appear, the last one wins.
853 */
854
855 p = b_head(&check->bi);
856 while (*p && *p != '\n' && *p != '\r')
857 p++;
858
859 if (!*p) {
860 if (!last_read)
861 goto wait_more_data;
862
863 /* at least inform the admin that the agent is mis-behaving */
864 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
865 goto out;
866 }
867
868 *p = 0;
869 cmd = b_head(&check->bi);
870
871 while (*cmd) {
872 /* look for next word */
873 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
874 cmd++;
875 continue;
876 }
877
878 if (*cmd == '#') {
879 /* this is the beginning of a health status description,
880 * skip the sharp and blanks.
881 */
882 cmd++;
883 while (*cmd == '\t' || *cmd == ' ')
884 cmd++;
885 break;
886 }
887
888 /* find the end of the word so that we have a null-terminated
889 * word between <cmd> and <p>.
890 */
891 p = cmd + 1;
892 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
893 p++;
894 if (*p)
895 *p++ = 0;
896
897 /* first, health statuses */
898 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100899 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200900 status = HCHK_STATUS_L7OKD;
901 hs = cmd;
902 }
903 else if (strcasecmp(cmd, "down") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100904 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200905 status = HCHK_STATUS_L7STS;
906 hs = cmd;
907 }
908 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100909 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200910 status = HCHK_STATUS_L7STS;
911 hs = cmd;
912 }
913 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100914 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200915 status = HCHK_STATUS_L7STS;
916 hs = cmd;
917 }
918 /* admin statuses */
919 else if (strcasecmp(cmd, "ready") == 0) {
920 as = cmd;
921 }
922 else if (strcasecmp(cmd, "drain") == 0) {
923 as = cmd;
924 }
925 else if (strcasecmp(cmd, "maint") == 0) {
926 as = cmd;
927 }
928 /* try to parse a weight here and keep the last one */
929 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
930 ps = cmd;
931 }
932 /* try to parse a maxconn here */
933 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
934 cs = cmd;
935 }
936 else {
937 /* keep a copy of the first error */
938 if (!err)
939 err = cmd;
940 }
941 /* skip to next word */
942 cmd = p;
943 }
944 /* here, cmd points either to \0 or to the beginning of a
945 * description. Skip possible leading spaces.
946 */
947 while (*cmd == ' ' || *cmd == '\n')
948 cmd++;
949
950 /* First, update the admin status so that we avoid sending other
951 * possibly useless warnings and can also update the health if
952 * present after going back up.
953 */
954 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200955 if (strcasecmp(as, "drain") == 0) {
956 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200957 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200958 }
959 else if (strcasecmp(as, "maint") == 0) {
960 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200961 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200962 }
963 else {
964 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200965 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200966 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200967 }
968
969 /* now change weights */
970 if (ps) {
971 const char *msg;
972
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500973 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200974 msg = server_parse_weight_change_request(check->server, ps);
975 if (!wrn || !*wrn)
976 wrn = msg;
977 }
978
979 if (cs) {
980 const char *msg;
981
982 cs += strlen("maxconn:");
983
Christopher Faulet147b8c92021-04-10 09:00:38 +0200984 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury Denoyelle02742862021-06-18 11:11:36 +0200985 /* This is safe to call server_parse_maxconn_change_request
986 * because the server lock is held during the check.
987 */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200988 msg = server_parse_maxconn_change_request(check->server, cs);
989 if (!wrn || !*wrn)
990 wrn = msg;
991 }
992
993 /* and finally health status */
994 if (hs) {
995 /* We'll report some of the warnings and errors we have
996 * here. Down reports are critical, we leave them untouched.
997 * Lack of report, or report of 'UP' leaves the room for
998 * ERR first, then WARN.
999 */
1000 const char *msg = cmd;
1001 struct buffer *t;
1002
1003 if (!*msg || status == HCHK_STATUS_L7OKD) {
1004 if (err && *err)
1005 msg = err;
1006 else if (wrn && *wrn)
1007 msg = wrn;
1008 }
1009
1010 t = get_trash_chunk();
1011 chunk_printf(t, "via agent : %s%s%s%s",
1012 hs, *msg ? " (" : "",
1013 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001014 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001015 set_server_check_status(check, status, t->area);
1016 }
1017 else if (err && *err) {
1018 /* No status change but we'd like to report something odd.
1019 * Just report the current state and copy the message.
1020 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001021 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001022 chunk_printf(&trash, "agent reports an error : %s", err);
1023 set_server_check_status(check, status/*check->status*/, trash.area);
1024 }
1025 else if (wrn && *wrn) {
1026 /* No status change but we'd like to report something odd.
1027 * Just report the current state and copy the message.
1028 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001029 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001030 chunk_printf(&trash, "agent warns : %s", wrn);
1031 set_server_check_status(check, status/*check->status*/, trash.area);
1032 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001033 else {
1034 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001035 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001036 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001037
1038 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001039 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001040 return ret;
1041
1042 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001043 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001044 ret = TCPCHK_EVAL_WAIT;
1045 goto out;
1046}
1047
1048/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1049 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1050 * TCPCHK_EVAL_STOP if an error occurred.
1051 */
1052enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1053{
1054 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1055 struct tcpcheck_connect *connect = &rule->connect;
1056 struct proxy *proxy = check->proxy;
1057 struct server *s = check->server;
1058 struct task *t = check->task;
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001059 struct connection *conn = cs_conn(check->cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001060 struct protocol *proto;
1061 struct xprt_ops *xprt;
1062 struct tcpcheck_rule *next;
1063 int status, port;
1064
Christopher Faulet147b8c92021-04-10 09:00:38 +02001065 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1066
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001067 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1068
1069 /* current connection already created, check if it is established or not */
1070 if (conn) {
1071 if (conn->flags & CO_FL_WAIT_XPRT) {
1072 /* We are still waiting for the connection establishment */
1073 if (next && next->action == TCPCHK_ACT_SEND) {
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001074 if (!(check->cs->wait_event.events & SUB_RETRY_SEND))
1075 conn->mux->subscribe(check->cs, SUB_RETRY_SEND, &check->cs->wait_event);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001076 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001077 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001078 }
1079 else
1080 ret = tcpcheck_eval_recv(check, rule);
1081 }
1082 goto out;
1083 }
1084
1085 /* Note: here check->cs = cs = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001086
Christopher Fauletb381a502020-11-25 13:47:00 +01001087 /* Always release input and output buffer when a new connect is evaluated */
1088 check_release_buf(check, &check->bi);
1089 check_release_buf(check, &check->bo);
1090
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001091 /* No connection, prepare a new one */
Christopher Fauletdd2d0d82021-12-20 09:34:32 +01001092 conn = conn_new((s ? &s->obj_type : &proxy->obj_type));
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001093 if (!conn) {
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001094 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1095 tcpcheck_get_step_id(check, rule));
1096 if (rule->comment)
1097 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1098 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1099 ret = TCPCHK_EVAL_STOP;
1100 TRACE_ERROR("conn-stream allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001101 goto out;
1102 }
Christopher Faulet070b91b2022-03-31 19:27:18 +02001103 if (cs_attach_mux(check->cs, NULL, conn) < 0) {
1104 TRACE_ERROR("mux attach error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
1105 conn_free(conn);
1106 conn = NULL;
1107 status = SF_ERR_RESOURCE;
1108 goto fail_check;
1109 }
Christopher Fauleta9e8b392022-03-23 11:01:09 +01001110 conn->ctx = check->cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001111 conn_set_owner(conn, check->sess, NULL);
1112
Willy Tarreau51cd5952020-06-05 12:25:38 +02001113 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001114 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001115 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001116 status = SF_ERR_RESOURCE;
1117 goto fail_check;
1118 }
1119
1120 /* connect to the connect rule addr if specified, otherwise the check
1121 * addr if specified on the server. otherwise, use the server addr (it
1122 * MUST exist at this step).
1123 */
1124 *conn->dst = (is_addr(&connect->addr)
1125 ? connect->addr
1126 : (is_addr(&check->addr) ? check->addr : s->addr));
Willy Tarreau14e7f292021-10-27 17:41:07 +02001127 proto = protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001128
1129 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001130 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001131 port = connect->port;
1132 if (!port && connect->port_expr) {
1133 struct sample *smp;
1134
1135 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1136 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1137 connect->port_expr, SMP_T_SINT);
1138 if (smp)
1139 port = smp->data.u.sint;
1140 }
1141 if (!port && is_inet_addr(&connect->addr))
1142 port = get_host_port(&connect->addr);
1143 if (!port && check->port)
1144 port = check->port;
1145 if (!port && is_inet_addr(&check->addr))
1146 port = get_host_port(&check->addr);
1147 if (!port) {
1148 /* The server MUST exist here */
1149 port = s->svc_port;
1150 }
1151 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001152 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001153
1154 xprt = ((connect->options & TCPCHK_OPT_SSL)
1155 ? xprt_get(XPRT_SSL)
1156 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1157
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001158 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001159 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001160 status = SF_ERR_RESOURCE;
1161 goto fail_check;
1162 }
1163
Christopher Fauletf7177272020-10-02 13:41:55 +02001164 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1165 conn->send_proxy_ofs = 1;
1166 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001167 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001168 }
1169 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1170 conn->send_proxy_ofs = 1;
1171 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001172 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001173 }
1174
1175 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1176 conn->send_proxy_ofs = 1;
1177 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001178 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001179 }
1180 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1181 conn->send_proxy_ofs = 1;
1182 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001183 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001184 }
1185
Willy Tarreau51cd5952020-06-05 12:25:38 +02001186 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001187 if (proto && proto->connect) {
1188 int flags = 0;
1189
1190 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1191 flags |= CONNECT_HAS_DATA;
1192 if (!next || next->action != TCPCHK_ACT_EXPECT)
1193 flags |= CONNECT_DELACK_ALWAYS;
1194 status = proto->connect(conn, flags);
1195 }
1196
1197 if (status != SF_ERR_NONE)
1198 goto fail_check;
1199
Christopher Faulet21ddc742020-07-01 15:26:14 +02001200 conn_set_private(conn);
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001201 conn->ctx = check->cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001202
Willy Tarreau51cd5952020-06-05 12:25:38 +02001203#ifdef USE_OPENSSL
1204 if (connect->sni)
1205 ssl_sock_set_servername(conn, connect->sni);
1206 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1207 ssl_sock_set_servername(conn, s->check.sni);
1208
1209 if (connect->alpn)
1210 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1211 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1212 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1213#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001214
Willy Tarreaue2226792022-04-11 18:04:33 +02001215 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER) && !(conn->flags & CO_FL_FDLESS)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001216 /* Some servers don't like reset on close */
Christopher Faulet897d6122021-12-17 17:28:35 +01001217 HA_ATOMIC_AND(&fdtab[conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001218 }
1219
1220 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1221 if (xprt_add_hs(conn) < 0)
1222 status = SF_ERR_RESOURCE;
1223 }
1224
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001225 if (conn_xprt_start(conn) < 0) {
1226 status = SF_ERR_RESOURCE;
1227 goto fail_check;
1228 }
1229
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001230 /* The mux may be initialized now if there isn't server attached to the
1231 * check (email alerts) or if there is a mux proto specified or if there
1232 * is no alpn.
1233 */
1234 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1235 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1236 const struct mux_ops *mux_ops;
1237
Christopher Faulet147b8c92021-04-10 09:00:38 +02001238 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001239 if (connect->mux_proto)
1240 mux_ops = connect->mux_proto->mux;
1241 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1242 mux_ops = check->mux_proto->mux;
1243 else {
1244 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1245 ? PROTO_MODE_HTTP
1246 : PROTO_MODE_TCP);
1247
1248 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1249 }
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001250 if (mux_ops && conn_install_mux(conn, mux_ops, check->cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001251 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001252 status = SF_ERR_INTERNAL;
1253 goto fail_check;
1254 }
1255 }
1256
Willy Tarreau51cd5952020-06-05 12:25:38 +02001257 fail_check:
1258 /* It can return one of :
1259 * - SF_ERR_NONE if everything's OK
1260 * - SF_ERR_SRVTO if there are no more servers
1261 * - SF_ERR_SRVCL if the connection was refused by the server
1262 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1263 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1264 * - SF_ERR_INTERNAL for any other purely internal errors
1265 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1266 * Note that we try to prevent the network stack from sending the ACK during the
1267 * connect() when a pure TCP check is used (without PROXY protocol).
1268 */
1269 switch (status) {
1270 case SF_ERR_NONE:
1271 /* we allow up to min(inter, timeout.connect) for a connection
1272 * to establish but only when timeout.check is set as it may be
1273 * to short for a full check otherwise
1274 */
1275 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1276
1277 if (proxy->timeout.check && proxy->timeout.connect) {
1278 int t_con = tick_add(now_ms, proxy->timeout.connect);
1279 t->expire = tick_first(t->expire, t_con);
1280 }
1281 break;
1282 case SF_ERR_SRVTO: /* ETIMEDOUT */
1283 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1284 case SF_ERR_PRXCOND:
1285 case SF_ERR_RESOURCE:
1286 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001287 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check, 0, 0, (size_t[]){status});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001288 chk_report_conn_err(check, errno, 0);
1289 ret = TCPCHK_EVAL_STOP;
1290 goto out;
1291 }
1292
1293 /* don't do anything until the connection is established */
1294 if (conn->flags & CO_FL_WAIT_XPRT) {
1295 if (conn->mux) {
1296 if (next && next->action == TCPCHK_ACT_SEND)
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001297 conn->mux->subscribe(check->cs, SUB_RETRY_SEND, &check->cs->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001298 else
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001299 conn->mux->subscribe(check->cs, SUB_RETRY_RECV, &check->cs->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001300 }
1301 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001302 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001303 goto out;
1304 }
1305
1306 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001307 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001308 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001309 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001310 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001311
1312 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1313 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1314
Christopher Faulet147b8c92021-04-10 09:00:38 +02001315 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001316 return ret;
1317}
1318
1319/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1320 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1321 * TCPCHK_EVAL_STOP if an error occurred.
1322 */
1323enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1324{
1325 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1326 struct tcpcheck_send *send = &rule->send;
1327 struct conn_stream *cs = check->cs;
Christopher Faulet693b23b2022-02-28 09:09:05 +01001328 struct connection *conn = __cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001329 struct buffer *tmp = NULL;
1330 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001331 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001332
Christopher Faulet147b8c92021-04-10 09:00:38 +02001333 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1334
Christopher Fauletb381a502020-11-25 13:47:00 +01001335 if (check->state & CHK_ST_OUT_ALLOC) {
1336 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001337 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001338 goto out;
1339 }
1340
1341 if (!check_get_buf(check, &check->bo)) {
1342 check->state |= CHK_ST_OUT_ALLOC;
1343 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001344 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001345 goto out;
1346 }
1347
Christopher Faulet39066c22020-11-25 13:34:51 +01001348 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001349 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001350 TRACE_DEVEL("Data still pending, try to send it now", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Christopher Faulet39066c22020-11-25 13:34:51 +01001351 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001352 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001353
Christopher Fauletb381a502020-11-25 13:47:00 +01001354 /* Always release input buffer when a new send is evaluated */
1355 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001356
1357 switch (send->type) {
1358 case TCPCHK_SEND_STRING:
1359 case TCPCHK_SEND_BINARY:
1360 if (istlen(send->data) >= b_size(&check->bo)) {
1361 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1362 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1363 tcpcheck_get_step_id(check, rule));
1364 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1365 ret = TCPCHK_EVAL_STOP;
1366 goto out;
1367 }
1368 b_putist(&check->bo, send->data);
1369 break;
1370 case TCPCHK_SEND_STRING_LF:
1371 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1372 if (!b_data(&check->bo))
1373 goto out;
1374 break;
1375 case TCPCHK_SEND_BINARY_LF: {
1376 int len = b_size(&check->bo);
1377
1378 tmp = alloc_trash_chunk();
1379 if (!tmp)
1380 goto error_lf;
1381 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1382 if (!b_data(tmp))
1383 goto out;
1384 tmp->area[tmp->data] = '\0';
1385 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1386 goto error_lf;
1387 check->bo.data = len;
1388 break;
1389 }
1390 case TCPCHK_SEND_HTTP: {
1391 struct htx_sl *sl;
1392 struct ist meth, uri, vsn, clen, body;
1393 unsigned int slflags = 0;
1394
1395 tmp = alloc_trash_chunk();
1396 if (!tmp)
1397 goto error_htx;
1398
1399 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1400 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1401 : http_known_methods[send->http.meth.meth]);
1402 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1403 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1404 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1405 }
1406 else
1407 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1408 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1409
1410 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1411 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1412 slflags |= HTX_SL_F_VER_11;
1413 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1414 if (!isttest(send->http.body))
1415 slflags |= HTX_SL_F_BODYLESS;
1416
1417 htx = htx_from_buf(&check->bo);
1418 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1419 if (!sl)
1420 goto error_htx;
1421 sl->info.req.meth = send->http.meth.meth;
1422 if (!http_update_host(htx, sl, uri))
1423 goto error_htx;
1424
1425 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1426 struct tcpcheck_http_hdr *hdr;
1427 struct ist hdr_value;
1428
1429 list_for_each_entry(hdr, &send->http.hdrs, list) {
1430 chunk_reset(tmp);
1431 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1432 if (!b_data(tmp))
1433 continue;
1434 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1435 if (!htx_add_header(htx, hdr->name, hdr_value))
1436 goto error_htx;
1437 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1438 if (!http_update_authority(htx, sl, hdr_value))
1439 goto error_htx;
1440 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001441 if (isteqi(hdr->name, ist("connection")))
1442 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001443 }
1444
1445 }
1446 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1447 chunk_reset(tmp);
1448 httpchk_build_status_header(check->server, tmp);
1449 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1450 goto error_htx;
1451 }
1452
1453
1454 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1455 chunk_reset(tmp);
1456 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1457 body = ist2(b_orig(tmp), b_data(tmp));
1458 }
1459 else
1460 body = send->http.body;
1461 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1462
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001463 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001464 !htx_add_header(htx, ist("Content-length"), clen))
1465 goto error_htx;
1466
1467
1468 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001469 (istlen(body) && !htx_add_data_atonce(htx, body)))
1470 goto error_htx;
1471
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001472 /* no more data are expected */
1473 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001474 htx_to_buf(htx, &check->bo);
1475 break;
1476 }
1477 case TCPCHK_SEND_UNDEF:
1478 /* Should never happen. */
1479 ret = TCPCHK_EVAL_STOP;
1480 goto out;
1481 };
1482
Christopher Faulet39066c22020-11-25 13:34:51 +01001483 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001484 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001485 if (conn->mux->snd_buf(cs, &check->bo,
1486 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Fauletb041b232022-03-24 10:27:02 +01001487 if ((conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001488 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001489 TRACE_DEVEL("connection error during send", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001490 goto out;
1491 }
1492 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001493 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001494 conn->mux->subscribe(cs, SUB_RETRY_SEND, &cs->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001495 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001496 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001497 goto out;
1498 }
1499
1500 out:
1501 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001502 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1503 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001504
1505 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001506 return ret;
1507
1508 error_htx:
1509 if (htx) {
1510 htx_reset(htx);
1511 htx_to_buf(htx, &check->bo);
1512 }
1513 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1514 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001515 TRACE_ERROR("failed to build HTTP request", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001516 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1517 ret = TCPCHK_EVAL_STOP;
1518 goto out;
1519
1520 error_lf:
1521 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1522 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001523 TRACE_ERROR("failed to build log-format string", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001524 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1525 ret = TCPCHK_EVAL_STOP;
1526 goto out;
1527
1528}
1529
1530/* Try to receive data before evaluating a tcp-check expect rule. Returns
1531 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1532 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1533 * TCPCHK_EVAL_STOP if an error occurred.
1534 */
1535enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1536{
1537 struct conn_stream *cs = check->cs;
Christopher Faulet693b23b2022-02-28 09:09:05 +01001538 struct connection *conn = __cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001539 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1540 size_t max, read, cur_read = 0;
1541 int is_empty;
1542 int read_poll = MAX_READ_POLL_LOOPS;
1543
Christopher Faulet147b8c92021-04-10 09:00:38 +02001544 TRACE_ENTER(CHK_EV_RX_DATA, check);
1545
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001546 if (cs->wait_event.events & SUB_RETRY_RECV) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001547 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001548 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001549 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001550
Christopher Fauletb041b232022-03-24 10:27:02 +01001551 if (cs->endp->flags & CS_EP_EOS)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001552 goto end_recv;
1553
Christopher Faulet147b8c92021-04-10 09:00:38 +02001554 if (check->state & CHK_ST_IN_ALLOC) {
1555 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001556 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001557 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001558
1559 if (!check_get_buf(check, &check->bi)) {
1560 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001561 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001562 goto wait_more_data;
1563 }
1564
Willy Tarreau51cd5952020-06-05 12:25:38 +02001565 /* errors on the connection and the conn-stream were already checked */
1566
1567 /* prepare to detect if the mux needs more room */
Christopher Fauletb041b232022-03-24 10:27:02 +01001568 cs->endp->flags &= ~CS_EP_WANT_ROOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001569
Christopher Fauletb041b232022-03-24 10:27:02 +01001570 while ((cs->endp->flags & CS_EP_RCV_MORE) ||
1571 (!(conn->flags & CO_FL_ERROR) && !(cs->endp->flags & (CS_EP_ERROR|CS_EP_EOS)))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001572 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1573 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1574 cur_read += read;
1575 if (!read ||
Christopher Fauletb041b232022-03-24 10:27:02 +01001576 (cs->endp->flags & CS_EP_WANT_ROOM) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001577 (--read_poll <= 0) ||
1578 (read < max && read >= global.tune.recv_enough))
1579 break;
1580 }
1581
1582 end_recv:
1583 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Christopher Fauletb041b232022-03-24 10:27:02 +01001584 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001585 /* Report network errors only if we got no other data. Otherwise
1586 * we'll let the upper layers decide whether the response is OK
1587 * or not. It is very common that an RST sent by the server is
1588 * reported as an error just after the last data chunk.
1589 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001590 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001591 goto stop;
1592 }
1593 if (!cur_read) {
Christopher Fauletb041b232022-03-24 10:27:02 +01001594 if (cs->endp->flags & CS_EP_EOI) {
Christopher Fauletd16e7dd2021-10-20 13:53:38 +02001595 /* If EOI is set, it means there is a response or an error */
1596 goto out;
1597 }
Christopher Fauletb041b232022-03-24 10:27:02 +01001598 if (!(cs->endp->flags & (CS_EP_WANT_ROOM|CS_EP_ERROR|CS_EP_EOS))) {
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001599 conn->mux->subscribe(cs, SUB_RETRY_RECV, &cs->wait_event);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001600 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001601 goto wait_more_data;
1602 }
1603 if (is_empty) {
1604 int status;
1605
1606 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1607 tcpcheck_get_step_id(check, rule));
1608 if (rule->comment)
1609 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1610
Christopher Faulet147b8c92021-04-10 09:00:38 +02001611 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001612 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1613 set_server_check_status(check, status, trash.area);
1614 goto stop;
1615 }
1616 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001617 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001618
1619 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001620 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1621 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001622
1623 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001624 return ret;
1625
1626 stop:
1627 ret = TCPCHK_EVAL_STOP;
1628 goto out;
1629
1630 wait_more_data:
1631 ret = TCPCHK_EVAL_WAIT;
1632 goto out;
1633}
1634
1635/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1636 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1637 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1638 * error occurred.
1639 */
1640enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1641{
1642 struct htx *htx = htxbuf(&check->bi);
1643 struct htx_sl *sl;
1644 struct htx_blk *blk;
1645 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1646 struct tcpcheck_expect *expect = &rule->expect;
1647 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1648 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1649 struct ist desc = IST_NULL;
1650 int i, match, inverse;
1651
Christopher Faulet147b8c92021-04-10 09:00:38 +02001652 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1653
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001654 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001655
1656 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001657 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001658 status = HCHK_STATUS_L7RSP;
1659 goto error;
1660 }
1661
1662 if (htx_is_empty(htx)) {
1663 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001664 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001665 status = HCHK_STATUS_L7RSP;
1666 goto error;
1667 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001668 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001669 goto wait_more_data;
1670 }
1671
1672 sl = http_get_stline(htx);
1673 check->code = sl->info.res.status;
1674
1675 if (check->server &&
1676 (check->server->proxy->options & PR_O_DISABLE404) &&
1677 (check->server->next_state != SRV_ST_STOPPED) &&
1678 (check->code == 404)) {
1679 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001680 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001681 goto out;
1682 }
1683
1684 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1685 /* Make GCC happy ; initialize match to a failure state. */
1686 match = inverse;
1687 status = expect->err_status;
1688
1689 switch (expect->type) {
1690 case TCPCHK_EXPECT_HTTP_STATUS:
1691 match = 0;
1692 for (i = 0; i < expect->codes.num; i++) {
1693 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1694 sl->info.res.status <= expect->codes.codes[i][1]) {
1695 match = 1;
1696 break;
1697 }
1698 }
1699
1700 /* Set status and description in case of error */
1701 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1702 if (LIST_ISEMPTY(&expect->onerror_fmt))
1703 desc = htx_sl_res_reason(sl);
1704 break;
1705 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1706 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1707
1708 /* Set status and description in case of error */
1709 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1710 if (LIST_ISEMPTY(&expect->onerror_fmt))
1711 desc = htx_sl_res_reason(sl);
1712 break;
1713
1714 case TCPCHK_EXPECT_HTTP_HEADER: {
1715 struct http_hdr_ctx ctx;
1716 struct ist npat, vpat, value;
1717 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1718
1719 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1720 nbuf = alloc_trash_chunk();
1721 if (!nbuf) {
1722 status = HCHK_STATUS_L7RSP;
1723 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001724 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001725 goto error;
1726 }
1727 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1728 if (!b_data(nbuf)) {
1729 status = HCHK_STATUS_L7RSP;
1730 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001731 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001732 goto error;
1733 }
1734 npat = ist2(b_orig(nbuf), b_data(nbuf));
1735 }
1736 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1737 npat = expect->hdr.name;
1738
1739 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1740 vbuf = alloc_trash_chunk();
1741 if (!vbuf) {
1742 status = HCHK_STATUS_L7RSP;
1743 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001744 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001745 goto error;
1746 }
1747 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1748 if (!b_data(vbuf)) {
1749 status = HCHK_STATUS_L7RSP;
1750 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001751 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001752 goto error;
1753 }
1754 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1755 }
1756 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1757 vpat = expect->hdr.value;
1758
1759 match = 0;
1760 ctx.blk = NULL;
1761 while (1) {
1762 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1763 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1764 if (!http_find_str_header(htx, npat, &ctx, full))
1765 goto end_of_match;
1766 break;
1767 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1768 if (!http_find_pfx_header(htx, npat, &ctx, full))
1769 goto end_of_match;
1770 break;
1771 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1772 if (!http_find_sfx_header(htx, npat, &ctx, full))
1773 goto end_of_match;
1774 break;
1775 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1776 if (!http_find_sub_header(htx, npat, &ctx, full))
1777 goto end_of_match;
1778 break;
1779 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1780 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1781 goto end_of_match;
1782 break;
1783 default:
1784 /* should never happen */
1785 goto end_of_match;
1786 }
1787
1788 /* A header has matched the name pattern, let's test its
1789 * value now (always defined from there). If there is no
1790 * value pattern, it is a good match.
1791 */
1792
1793 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1794 match = 1;
1795 goto end_of_match;
1796 }
1797
1798 value = ctx.value;
1799 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1800 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1801 if (isteq(value, vpat)) {
1802 match = 1;
1803 goto end_of_match;
1804 }
1805 break;
1806 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1807 if (istlen(value) < istlen(vpat))
1808 break;
1809 value = ist2(istptr(value), istlen(vpat));
1810 if (isteq(value, vpat)) {
1811 match = 1;
1812 goto end_of_match;
1813 }
1814 break;
1815 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1816 if (istlen(value) < istlen(vpat))
1817 break;
Tim Duesterhus4c8f75f2021-11-06 15:14:44 +01001818 value = ist2(istend(value) - istlen(vpat), istlen(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001819 if (isteq(value, vpat)) {
1820 match = 1;
1821 goto end_of_match;
1822 }
1823 break;
1824 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1825 if (isttest(istist(value, vpat))) {
1826 match = 1;
1827 goto end_of_match;
1828 }
1829 break;
1830 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1831 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1832 match = 1;
1833 goto end_of_match;
1834 }
1835 break;
1836 }
1837 }
1838
1839 end_of_match:
1840 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1841 if (LIST_ISEMPTY(&expect->onerror_fmt))
1842 desc = htx_sl_res_reason(sl);
1843 break;
1844 }
1845
1846 case TCPCHK_EXPECT_HTTP_BODY:
1847 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1848 case TCPCHK_EXPECT_HTTP_BODY_LF:
1849 match = 0;
1850 chunk_reset(&trash);
1851 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1852 enum htx_blk_type type = htx_get_blk_type(blk);
1853
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001854 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001855 break;
1856 if (type == HTX_BLK_DATA) {
1857 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1858 break;
1859 }
1860 }
1861
1862 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001863 if (!last_read) {
1864 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001865 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001866 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001867 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1868 if (LIST_ISEMPTY(&expect->onerror_fmt))
1869 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001870 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001871 goto error;
1872 }
1873
1874 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1875 tmp = alloc_trash_chunk();
1876 if (!tmp) {
1877 status = HCHK_STATUS_L7RSP;
1878 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001879 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001880 goto error;
1881 }
1882 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1883 if (!b_data(tmp)) {
1884 status = HCHK_STATUS_L7RSP;
1885 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001886 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001887 goto error;
1888 }
1889 }
1890
1891 if (!last_read &&
1892 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1893 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1894 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1895 ret = TCPCHK_EVAL_WAIT;
1896 goto out;
1897 }
1898
1899 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1900 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1901 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1902 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1903 else
1904 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1905
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001906 /* Wait for more data on mismatch only if no minimum is defined (-1),
1907 * otherwise the absence of match is already conclusive.
1908 */
1909 if (!match && !last_read && (expect->min_recv == -1)) {
1910 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001911 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001912 goto out;
1913 }
1914
Willy Tarreau51cd5952020-06-05 12:25:38 +02001915 /* Set status and description in case of error */
1916 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1917 if (LIST_ISEMPTY(&expect->onerror_fmt))
1918 desc = (inverse
1919 ? ist("HTTP check matched unwanted content")
1920 : ist("HTTP content check did not match"));
1921 break;
1922
1923
1924 default:
1925 /* should never happen */
1926 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1927 goto error;
1928 }
1929
Christopher Faulet147b8c92021-04-10 09:00:38 +02001930 if (!(match ^ inverse)) {
1931 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001932 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001933 }
1934
1935 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001936
1937 out:
1938 free_trash_chunk(tmp);
1939 free_trash_chunk(nbuf);
1940 free_trash_chunk(vbuf);
1941 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001942 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001943 return ret;
1944
1945 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001946 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001947 ret = TCPCHK_EVAL_STOP;
1948 msg = alloc_trash_chunk();
1949 if (msg)
1950 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1951 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1952 goto out;
1953
1954 wait_more_data:
1955 ret = TCPCHK_EVAL_WAIT;
1956 goto out;
1957}
1958
1959/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1960 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1961 * if an error occurred.
1962 */
1963enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1964{
1965 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1966 struct tcpcheck_expect *expect = &rule->expect;
1967 struct buffer *msg = NULL, *tmp = NULL;
1968 struct ist desc = IST_NULL;
1969 enum healthcheck_status status;
1970 int match, inverse;
1971
Christopher Faulet147b8c92021-04-10 09:00:38 +02001972 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1973
Willy Tarreau51cd5952020-06-05 12:25:38 +02001974 last_read |= b_full(&check->bi);
1975
1976 /* The current expect might need more data than the previous one, check again
1977 * that the minimum amount data required to match is respected.
1978 */
1979 if (!last_read) {
1980 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1981 (b_data(&check->bi) < istlen(expect->data))) {
1982 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001983 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001984 goto out;
1985 }
1986 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1987 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001988 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001989 goto out;
1990 }
1991 }
1992
1993 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1994 /* Make GCC happy ; initialize match to a failure state. */
1995 match = inverse;
1996 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1997
1998 switch (expect->type) {
1999 case TCPCHK_EXPECT_STRING:
2000 case TCPCHK_EXPECT_BINARY:
2001 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2002 break;
2003 case TCPCHK_EXPECT_STRING_REGEX:
2004 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2005 break;
2006
2007 case TCPCHK_EXPECT_BINARY_REGEX:
2008 chunk_reset(&trash);
2009 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2010 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2011 break;
2012
2013 case TCPCHK_EXPECT_STRING_LF:
2014 case TCPCHK_EXPECT_BINARY_LF:
2015 match = 0;
2016 tmp = alloc_trash_chunk();
2017 if (!tmp) {
2018 status = HCHK_STATUS_L7RSP;
2019 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002020 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002021 goto error;
2022 }
2023 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2024 if (!b_data(tmp)) {
2025 status = HCHK_STATUS_L7RSP;
2026 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002027 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002028 goto error;
2029 }
2030 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2031 int len = tmp->data;
2032 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2033 status = HCHK_STATUS_L7RSP;
2034 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002035 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002036 goto error;
2037 }
2038 tmp->data = len;
2039 }
2040 if (b_data(&check->bi) < tmp->data) {
2041 if (!last_read) {
2042 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002043 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002044 goto out;
2045 }
2046 break;
2047 }
2048 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2049 break;
2050
2051 case TCPCHK_EXPECT_CUSTOM:
2052 if (expect->custom)
2053 ret = expect->custom(check, rule, last_read);
2054 goto out;
2055 default:
2056 /* Should never happen. */
2057 ret = TCPCHK_EVAL_STOP;
2058 goto out;
2059 }
2060
2061
2062 /* Wait for more data on mismatch only if no minimum is defined (-1),
2063 * otherwise the absence of match is already conclusive.
2064 */
2065 if (!match && !last_read && (expect->min_recv == -1)) {
2066 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002067 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002068 goto out;
2069 }
2070
2071 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002072 if (match ^ inverse) {
2073 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002074 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002075 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002076
2077 error:
2078 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002079 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002080 ret = TCPCHK_EVAL_STOP;
2081 msg = alloc_trash_chunk();
2082 if (msg)
2083 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2084 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2085 free_trash_chunk(msg);
2086
2087 out:
2088 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002089 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002090 return ret;
2091}
2092
2093/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2094 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2095 * waits.
2096 */
2097enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2098{
2099 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2100 struct act_rule *act_rule;
2101 enum act_return act_ret;
2102
2103 act_rule =rule->action_kw.rule;
2104 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2105 if (act_ret != ACT_RET_CONT) {
2106 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2107 tcpcheck_get_step_id(check, rule));
2108 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2109 ret = TCPCHK_EVAL_STOP;
2110 }
2111
2112 return ret;
2113}
2114
2115/* Executes a tcp-check ruleset. Note that this is called both from the
2116 * connection's wake() callback and from the check scheduling task. It returns
2117 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2118 * presenting the risk of an fd replacement.
2119 *
2120 * Please do NOT place any return statement in this function and only leave
2121 * via the out_end_tcpcheck label after setting retcode.
2122 */
2123int tcpcheck_main(struct check *check)
2124{
2125 struct tcpcheck_rule *rule;
2126 struct conn_stream *cs = check->cs;
2127 struct connection *conn = cs_conn(cs);
2128 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002129 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002130 enum tcpcheck_eval_ret eval_ret;
2131
2132 /* here, we know that the check is complete or that it failed */
2133 if (check->result != CHK_RES_UNKNOWN)
2134 goto out;
2135
Christopher Faulet147b8c92021-04-10 09:00:38 +02002136 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2137
Willy Tarreau51cd5952020-06-05 12:25:38 +02002138 /* Note: the conn-stream and the connection may only be undefined before
2139 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002140 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002141 */
2142
2143 /* 1- check for connection error, if any */
Christopher Fauletb041b232022-03-24 10:27:02 +01002144 if ((conn && conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002145 goto out_end_tcpcheck;
2146
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002147 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002148 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002149 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002150 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002151 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2152 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002153
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002154 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002155 * tcp-check variables */
2156 else {
2157 struct tcpcheck_var *var;
2158
2159 /* First evaluation, create a session */
2160 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2161 if (!check->sess) {
2162 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002163 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002164 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2165 goto out_end_tcpcheck;
2166 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002167 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002168 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2169
2170 /* Preset tcp-check variables */
2171 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2172 struct sample smp;
2173
2174 memset(&smp, 0, sizeof(smp));
2175 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2176 smp.data = var->data;
2177 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2178 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002179 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002180 }
2181
2182 /* Now evaluate the tcp-check rules */
2183
2184 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2185 check->code = 0;
2186 switch (rule->action) {
2187 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002188 /* Not the first connection, release it first */
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002189 if (cs_conn(cs) && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002190 check->state |= CHK_ST_CLOSE_CONN;
2191 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002192 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002193
2194 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002195
2196 /* We are still waiting the connection gets closed */
2197 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002198 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002199 eval_ret = TCPCHK_EVAL_WAIT;
2200 break;
2201 }
2202
Christopher Faulet147b8c92021-04-10 09:00:38 +02002203 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002204 eval_ret = tcpcheck_eval_connect(check, rule);
2205
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002206 /* Refresh connection */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002207 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002208 last_read = 0;
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002209 must_read = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002210 break;
2211 case TCPCHK_ACT_SEND:
2212 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002213 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002214 eval_ret = tcpcheck_eval_send(check, rule);
2215 must_read = 1;
2216 break;
2217 case TCPCHK_ACT_EXPECT:
2218 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002219 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002220 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002221 eval_ret = tcpcheck_eval_recv(check, rule);
2222 if (eval_ret == TCPCHK_EVAL_STOP)
2223 goto out_end_tcpcheck;
2224 else if (eval_ret == TCPCHK_EVAL_WAIT)
2225 goto out;
Christopher Fauletb041b232022-03-24 10:27:02 +01002226 last_read = ((conn->flags & CO_FL_ERROR) || (cs->endp->flags & (CS_EP_ERROR|CS_EP_EOS)));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002227 must_read = 0;
2228 }
2229
2230 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2231 ? tcpcheck_eval_expect_http(check, rule, last_read)
2232 : tcpcheck_eval_expect(check, rule, last_read));
2233
2234 if (eval_ret == TCPCHK_EVAL_WAIT) {
2235 check->current_step = rule->expect.head;
Christopher Fauletc95eaef2022-05-18 15:57:15 +02002236 if (!(cs->wait_event.events & SUB_RETRY_RECV))
2237 conn->mux->subscribe(cs, SUB_RETRY_RECV, &cs->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002238 }
2239 break;
2240 case TCPCHK_ACT_ACTION_KW:
2241 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002242 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002243 eval_ret = tcpcheck_eval_action_kw(check, rule);
2244 break;
2245 default:
2246 /* Otherwise, just go to the next one and don't update
2247 * the current step
2248 */
2249 eval_ret = TCPCHK_EVAL_CONTINUE;
2250 break;
2251 }
2252
2253 switch (eval_ret) {
2254 case TCPCHK_EVAL_CONTINUE:
2255 break;
2256 case TCPCHK_EVAL_WAIT:
2257 goto out;
2258 case TCPCHK_EVAL_STOP:
2259 goto out_end_tcpcheck;
2260 }
2261 }
2262
2263 /* All rules was evaluated */
2264 if (check->current_step) {
2265 rule = check->current_step;
2266
Christopher Faulet147b8c92021-04-10 09:00:38 +02002267 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2268
Willy Tarreau51cd5952020-06-05 12:25:38 +02002269 if (rule->action == TCPCHK_ACT_EXPECT) {
2270 struct buffer *msg;
2271 enum healthcheck_status status;
2272
2273 if (check->server &&
2274 (check->server->proxy->options & PR_O_DISABLE404) &&
2275 (check->server->next_state != SRV_ST_STOPPED) &&
2276 (check->code == 404)) {
2277 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002278 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002279 goto out_end_tcpcheck;
2280 }
2281
2282 msg = alloc_trash_chunk();
2283 if (msg)
2284 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2285 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2286 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2287 free_trash_chunk(msg);
2288 }
2289 else if (rule->action == TCPCHK_ACT_CONNECT) {
2290 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2291 enum healthcheck_status status = HCHK_STATUS_L4OK;
2292#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002293 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002294 status = HCHK_STATUS_L6OK;
2295#endif
2296 set_server_check_status(check, status, msg);
2297 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002298 else
2299 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002300 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002301 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002302 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002303 }
2304 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002305
2306 out_end_tcpcheck:
Christopher Fauletb041b232022-03-24 10:27:02 +01002307 if ((conn && conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002308 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002309 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002310 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002311
Christopher Fauletb381a502020-11-25 13:47:00 +01002312 /* the tcpcheck is finished, release in/out buffer now */
2313 check_release_buf(check, &check->bi);
2314 check_release_buf(check, &check->bo);
2315
Willy Tarreau51cd5952020-06-05 12:25:38 +02002316 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002317 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002318 return retcode;
2319}
2320
Willy Tarreaua631b862022-03-02 14:54:44 +01002321void tcp_check_keywords_register(struct action_kw_list *kw_list)
2322{
2323 LIST_APPEND(&tcp_check_keywords.list, &kw_list->list);
2324}
Willy Tarreau51cd5952020-06-05 12:25:38 +02002325
2326/**************************************************************************/
2327/******************* Internals to parse tcp-check rules *******************/
2328/**************************************************************************/
2329struct action_kw_list tcp_check_keywords = {
2330 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2331};
2332
2333/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2334 * returned on error.
2335 */
2336struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2337 struct list *rules, struct action_kw *kw,
2338 const char *file, int line, char **errmsg)
2339{
2340 struct tcpcheck_rule *chk = NULL;
2341 struct act_rule *actrule = NULL;
2342
Willy Tarreaud535f802021-10-11 08:49:26 +02002343 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002344 if (!actrule) {
2345 memprintf(errmsg, "out of memory");
2346 goto error;
2347 }
2348 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002349
2350 cur_arg++;
2351 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2352 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2353 goto error;
2354 }
2355
2356 chk = calloc(1, sizeof(*chk));
2357 if (!chk) {
2358 memprintf(errmsg, "out of memory");
2359 goto error;
2360 }
2361 chk->action = TCPCHK_ACT_ACTION_KW;
2362 chk->action_kw.rule = actrule;
2363 return chk;
2364
2365 error:
2366 free(actrule);
2367 return NULL;
2368}
2369
2370/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2371 * returned on error.
2372 */
2373struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2374 const char *file, int line, char **errmsg)
2375{
2376 struct tcpcheck_rule *chk = NULL;
2377 struct sockaddr_storage *sk = NULL;
2378 char *comment = NULL, *sni = NULL, *alpn = NULL;
2379 struct sample_expr *port_expr = NULL;
2380 const struct mux_proto_list *mux_proto = NULL;
2381 unsigned short conn_opts = 0;
2382 long port = 0;
2383 int alpn_len = 0;
2384
2385 list_for_each_entry(chk, rules, list) {
2386 if (chk->action == TCPCHK_ACT_CONNECT)
2387 break;
2388 if (chk->action == TCPCHK_ACT_COMMENT ||
2389 chk->action == TCPCHK_ACT_ACTION_KW ||
2390 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2391 continue;
2392
2393 memprintf(errmsg, "first step MUST also be a 'connect', "
2394 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2395 "when there is a 'connect' step in the tcp-check ruleset");
2396 goto error;
2397 }
2398
2399 cur_arg++;
2400 while (*(args[cur_arg])) {
2401 if (strcmp(args[cur_arg], "default") == 0)
2402 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2403 else if (strcmp(args[cur_arg], "addr") == 0) {
2404 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002405
2406 if (!*(args[cur_arg+1])) {
2407 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2408 goto error;
2409 }
2410
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002411 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2412 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002413 if (!sk) {
2414 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2415 goto error;
2416 }
2417
Willy Tarreau51cd5952020-06-05 12:25:38 +02002418 cur_arg++;
2419 }
2420 else if (strcmp(args[cur_arg], "port") == 0) {
2421 const char *p, *end;
2422
2423 if (!*(args[cur_arg+1])) {
2424 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2425 goto error;
2426 }
2427 cur_arg++;
2428
2429 port = 0;
2430 release_sample_expr(port_expr);
2431 p = args[cur_arg]; end = p + strlen(p);
2432 port = read_uint(&p, end);
2433 if (p != end) {
2434 int idx = 0;
2435
2436 px->conf.args.ctx = ARGC_SRV;
2437 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002438 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002439
2440 if (!port_expr) {
2441 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2442 goto error;
2443 }
2444 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2445 memprintf(errmsg, "error detected while parsing port expression : "
2446 " fetch method '%s' extracts information from '%s', "
2447 "none of which is available here.\n",
2448 args[cur_arg], sample_src_names(port_expr->fetch->use));
2449 goto error;
2450 }
2451 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2452 }
2453 else if (port > 65535 || port < 1) {
2454 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2455 args[cur_arg]);
2456 goto error;
2457 }
2458 }
2459 else if (strcmp(args[cur_arg], "proto") == 0) {
2460 if (!*(args[cur_arg+1])) {
2461 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2462 goto error;
2463 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002464 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002465 if (!mux_proto) {
2466 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2467 goto error;
2468 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002469
2470 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2471 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2472 goto error;
2473 }
2474 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2475 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2476 goto error;
2477 }
2478
Willy Tarreau51cd5952020-06-05 12:25:38 +02002479 cur_arg++;
2480 }
2481 else if (strcmp(args[cur_arg], "comment") == 0) {
2482 if (!*(args[cur_arg+1])) {
2483 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2484 goto error;
2485 }
2486 cur_arg++;
2487 free(comment);
2488 comment = strdup(args[cur_arg]);
2489 if (!comment) {
2490 memprintf(errmsg, "out of memory");
2491 goto error;
2492 }
2493 }
2494 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2495 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2496 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2497 conn_opts |= TCPCHK_OPT_SOCKS4;
2498 else if (strcmp(args[cur_arg], "linger") == 0)
2499 conn_opts |= TCPCHK_OPT_LINGER;
2500#ifdef USE_OPENSSL
2501 else if (strcmp(args[cur_arg], "ssl") == 0) {
2502 px->options |= PR_O_TCPCHK_SSL;
2503 conn_opts |= TCPCHK_OPT_SSL;
2504 }
2505 else if (strcmp(args[cur_arg], "sni") == 0) {
2506 if (!*(args[cur_arg+1])) {
2507 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2508 goto error;
2509 }
2510 cur_arg++;
2511 free(sni);
2512 sni = strdup(args[cur_arg]);
2513 if (!sni) {
2514 memprintf(errmsg, "out of memory");
2515 goto error;
2516 }
2517 }
2518 else if (strcmp(args[cur_arg], "alpn") == 0) {
2519#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2520 free(alpn);
2521 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2522 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2523 goto error;
2524 }
2525 cur_arg++;
2526#else
2527 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2528 goto error;
2529#endif
2530 }
2531#endif /* USE_OPENSSL */
2532
2533 else {
2534 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2535#ifdef USE_OPENSSL
2536 ", 'ssl', 'sni', 'alpn'"
2537#endif /* USE_OPENSSL */
2538 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2539 args[cur_arg]);
2540 goto error;
2541 }
2542 cur_arg++;
2543 }
2544
2545 chk = calloc(1, sizeof(*chk));
2546 if (!chk) {
2547 memprintf(errmsg, "out of memory");
2548 goto error;
2549 }
2550 chk->action = TCPCHK_ACT_CONNECT;
2551 chk->comment = comment;
2552 chk->connect.port = port;
2553 chk->connect.options = conn_opts;
2554 chk->connect.sni = sni;
2555 chk->connect.alpn = alpn;
2556 chk->connect.alpn_len= alpn_len;
2557 chk->connect.port_expr= port_expr;
2558 chk->connect.mux_proto= mux_proto;
2559 if (sk)
2560 chk->connect.addr = *sk;
2561 return chk;
2562
2563 error:
2564 free(alpn);
2565 free(sni);
2566 free(comment);
2567 release_sample_expr(port_expr);
2568 return NULL;
2569}
2570
2571/* Parses and creates a tcp-check send rule. NULL is returned on error */
2572struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2573 const char *file, int line, char **errmsg)
2574{
2575 struct tcpcheck_rule *chk = NULL;
2576 char *comment = NULL, *data = NULL;
2577 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2578
2579 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2580 type = TCPCHK_SEND_BINARY_LF;
2581 else if (strcmp(args[cur_arg], "send-binary") == 0)
2582 type = TCPCHK_SEND_BINARY;
2583 else if (strcmp(args[cur_arg], "send-lf") == 0)
2584 type = TCPCHK_SEND_STRING_LF;
2585 else if (strcmp(args[cur_arg], "send") == 0)
2586 type = TCPCHK_SEND_STRING;
2587
2588 if (!*(args[cur_arg+1])) {
2589 memprintf(errmsg, "'%s' expects a %s as argument",
2590 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2591 goto error;
2592 }
2593
2594 data = args[cur_arg+1];
2595
2596 cur_arg += 2;
2597 while (*(args[cur_arg])) {
2598 if (strcmp(args[cur_arg], "comment") == 0) {
2599 if (!*(args[cur_arg+1])) {
2600 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2601 goto error;
2602 }
2603 cur_arg++;
2604 free(comment);
2605 comment = strdup(args[cur_arg]);
2606 if (!comment) {
2607 memprintf(errmsg, "out of memory");
2608 goto error;
2609 }
2610 }
2611 else {
2612 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2613 args[cur_arg]);
2614 goto error;
2615 }
2616 cur_arg++;
2617 }
2618
2619 chk = calloc(1, sizeof(*chk));
2620 if (!chk) {
2621 memprintf(errmsg, "out of memory");
2622 goto error;
2623 }
2624 chk->action = TCPCHK_ACT_SEND;
2625 chk->comment = comment;
2626 chk->send.type = type;
2627
2628 switch (chk->send.type) {
2629 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002630 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002631 if (!isttest(chk->send.data)) {
2632 memprintf(errmsg, "out of memory");
2633 goto error;
2634 }
2635 break;
2636 case TCPCHK_SEND_BINARY: {
2637 int len = chk->send.data.len;
2638 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2639 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2640 goto error;
2641 }
2642 chk->send.data.len = len;
2643 break;
2644 }
2645 case TCPCHK_SEND_STRING_LF:
2646 case TCPCHK_SEND_BINARY_LF:
2647 LIST_INIT(&chk->send.fmt);
2648 px->conf.args.ctx = ARGC_SRV;
2649 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2650 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2651 goto error;
2652 }
2653 break;
2654 case TCPCHK_SEND_HTTP:
2655 case TCPCHK_SEND_UNDEF:
2656 goto error;
2657 }
2658
2659 return chk;
2660
2661 error:
2662 free(chk);
2663 free(comment);
2664 return NULL;
2665}
2666
2667/* Parses and creates a http-check send rule. NULL is returned on error */
2668struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2669 const char *file, int line, char **errmsg)
2670{
2671 struct tcpcheck_rule *chk = NULL;
2672 struct tcpcheck_http_hdr *hdr = NULL;
2673 struct http_hdr hdrs[global.tune.max_http_hdr];
2674 char *meth = NULL, *uri = NULL, *vsn = NULL;
2675 char *body = NULL, *comment = NULL;
2676 unsigned int flags = 0;
2677 int i = 0, host_hdr = -1;
2678
2679 cur_arg++;
2680 while (*(args[cur_arg])) {
2681 if (strcmp(args[cur_arg], "meth") == 0) {
2682 if (!*(args[cur_arg+1])) {
2683 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2684 goto error;
2685 }
2686 cur_arg++;
2687 meth = args[cur_arg];
2688 }
2689 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2690 if (!*(args[cur_arg+1])) {
2691 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2692 goto error;
2693 }
2694 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2695 if (strcmp(args[cur_arg], "uri-lf") == 0)
2696 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2697 cur_arg++;
2698 uri = args[cur_arg];
2699 }
2700 else if (strcmp(args[cur_arg], "ver") == 0) {
2701 if (!*(args[cur_arg+1])) {
2702 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2703 goto error;
2704 }
2705 cur_arg++;
2706 vsn = args[cur_arg];
2707 }
2708 else if (strcmp(args[cur_arg], "hdr") == 0) {
2709 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2710 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2711 goto error;
2712 }
2713
2714 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2715 if (host_hdr >= 0) {
2716 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2717 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2718 goto error;
2719 }
2720 host_hdr = i;
2721 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002722 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002723 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2724 goto skip_hdr;
2725
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002726 hdrs[i].n = ist(args[cur_arg + 1]);
2727 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002728 i++;
2729 skip_hdr:
2730 cur_arg += 2;
2731 }
2732 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2733 if (!*(args[cur_arg+1])) {
2734 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2735 goto error;
2736 }
2737 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2738 if (strcmp(args[cur_arg], "body-lf") == 0)
2739 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2740 cur_arg++;
2741 body = args[cur_arg];
2742 }
2743 else if (strcmp(args[cur_arg], "comment") == 0) {
2744 if (!*(args[cur_arg+1])) {
2745 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2746 goto error;
2747 }
2748 cur_arg++;
2749 free(comment);
2750 comment = strdup(args[cur_arg]);
2751 if (!comment) {
2752 memprintf(errmsg, "out of memory");
2753 goto error;
2754 }
2755 }
2756 else {
2757 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2758 " but got '%s' as argument.", args[cur_arg]);
2759 goto error;
2760 }
2761 cur_arg++;
2762 }
2763
2764 hdrs[i].n = hdrs[i].v = IST_NULL;
2765
2766 chk = calloc(1, sizeof(*chk));
2767 if (!chk) {
2768 memprintf(errmsg, "out of memory");
2769 goto error;
2770 }
2771 chk->action = TCPCHK_ACT_SEND;
2772 chk->comment = comment; comment = NULL;
2773 chk->send.type = TCPCHK_SEND_HTTP;
2774 chk->send.http.flags = flags;
2775 LIST_INIT(&chk->send.http.hdrs);
2776
2777 if (meth) {
2778 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2779 chk->send.http.meth.str.area = strdup(meth);
2780 chk->send.http.meth.str.data = strlen(meth);
2781 if (!chk->send.http.meth.str.area) {
2782 memprintf(errmsg, "out of memory");
2783 goto error;
2784 }
2785 }
2786 if (uri) {
2787 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2788 LIST_INIT(&chk->send.http.uri_fmt);
2789 px->conf.args.ctx = ARGC_SRV;
2790 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2791 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2792 goto error;
2793 }
2794 }
2795 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002796 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002797 if (!isttest(chk->send.http.uri)) {
2798 memprintf(errmsg, "out of memory");
2799 goto error;
2800 }
2801 }
2802 }
2803 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002804 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002805 if (!isttest(chk->send.http.vsn)) {
2806 memprintf(errmsg, "out of memory");
2807 goto error;
2808 }
2809 }
2810 for (i = 0; istlen(hdrs[i].n); i++) {
2811 hdr = calloc(1, sizeof(*hdr));
2812 if (!hdr) {
2813 memprintf(errmsg, "out of memory");
2814 goto error;
2815 }
2816 LIST_INIT(&hdr->value);
2817 hdr->name = istdup(hdrs[i].n);
2818 if (!isttest(hdr->name)) {
2819 memprintf(errmsg, "out of memory");
2820 goto error;
2821 }
2822
2823 ist0(hdrs[i].v);
2824 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2825 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002826 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002827 hdr = NULL;
2828 }
2829
2830 if (body) {
2831 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2832 LIST_INIT(&chk->send.http.body_fmt);
2833 px->conf.args.ctx = ARGC_SRV;
2834 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2835 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2836 goto error;
2837 }
2838 }
2839 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002840 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002841 if (!isttest(chk->send.http.body)) {
2842 memprintf(errmsg, "out of memory");
2843 goto error;
2844 }
2845 }
2846 }
2847
2848 return chk;
2849
2850 error:
2851 free_tcpcheck_http_hdr(hdr);
2852 free_tcpcheck(chk, 0);
2853 free(comment);
2854 return NULL;
2855}
2856
2857/* Parses and creates a http-check comment rule. NULL is returned on error */
2858struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2859 const char *file, int line, char **errmsg)
2860{
2861 struct tcpcheck_rule *chk = NULL;
2862 char *comment = NULL;
2863
2864 if (!*(args[cur_arg+1])) {
2865 memprintf(errmsg, "expects a string as argument");
2866 goto error;
2867 }
2868 cur_arg++;
2869 comment = strdup(args[cur_arg]);
2870 if (!comment) {
2871 memprintf(errmsg, "out of memory");
2872 goto error;
2873 }
2874
2875 chk = calloc(1, sizeof(*chk));
2876 if (!chk) {
2877 memprintf(errmsg, "out of memory");
2878 goto error;
2879 }
2880 chk->action = TCPCHK_ACT_COMMENT;
2881 chk->comment = comment;
2882 return chk;
2883
2884 error:
2885 free(comment);
2886 return NULL;
2887}
2888
2889/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2890 * on error. <proto> is set to the right protocol flags (covered by the
2891 * TCPCHK_RULES_PROTO_CHK mask).
2892 */
2893struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2894 struct list *rules, unsigned int proto,
2895 const char *file, int line, char **errmsg)
2896{
2897 struct tcpcheck_rule *prev_check, *chk = NULL;
2898 struct sample_expr *status_expr = NULL;
2899 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2900 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2901 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2902 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2903 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2904 unsigned int flags = 0;
2905 long min_recv = -1;
2906 int inverse = 0;
2907
2908 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2909 if (!*(args[cur_arg+1])) {
2910 memprintf(errmsg, "expects at least a matching pattern as arguments");
2911 goto error;
2912 }
2913
2914 cur_arg++;
2915 while (*(args[cur_arg])) {
2916 int in_pattern = 0;
2917
2918 rescan:
2919 if (strcmp(args[cur_arg], "min-recv") == 0) {
2920 if (in_pattern) {
2921 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2922 goto error;
2923 }
2924 if (!*(args[cur_arg+1])) {
2925 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2926 goto error;
2927 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002928 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002929 cur_arg++;
2930 min_recv = atol(args[cur_arg]);
2931 if (min_recv < -1 || min_recv > INT_MAX) {
2932 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2933 goto error;
2934 }
2935 }
2936 else if (*(args[cur_arg]) == '!') {
2937 in_pattern = 1;
2938 while (*(args[cur_arg]) == '!') {
2939 inverse = !inverse;
2940 args[cur_arg]++;
2941 }
2942 if (!*(args[cur_arg]))
2943 cur_arg++;
2944 goto rescan;
2945 }
2946 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2947 if (type != TCPCHK_EXPECT_UNDEF) {
2948 memprintf(errmsg, "only on pattern expected");
2949 goto error;
2950 }
2951 if (proto != TCPCHK_RULES_HTTP_CHK)
2952 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2953 else
2954 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2955
2956 if (!*(args[cur_arg+1])) {
2957 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2958 goto error;
2959 }
2960 cur_arg++;
2961 pattern = args[cur_arg];
2962 }
2963 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2964 if (proto == TCPCHK_RULES_HTTP_CHK)
2965 goto bad_http_kw;
2966 if (type != TCPCHK_EXPECT_UNDEF) {
2967 memprintf(errmsg, "only on pattern expected");
2968 goto error;
2969 }
2970 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2971
2972 if (!*(args[cur_arg+1])) {
2973 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2974 goto error;
2975 }
2976 cur_arg++;
2977 pattern = args[cur_arg];
2978 }
2979 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2980 if (type != TCPCHK_EXPECT_UNDEF) {
2981 memprintf(errmsg, "only on pattern expected");
2982 goto error;
2983 }
2984 if (proto != TCPCHK_RULES_HTTP_CHK)
2985 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2986 else {
2987 if (*(args[cur_arg]) != 's')
2988 goto bad_http_kw;
2989 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2990 }
2991
2992 if (!*(args[cur_arg+1])) {
2993 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2994 goto error;
2995 }
2996 cur_arg++;
2997 pattern = args[cur_arg];
2998 }
2999 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3000 if (proto != TCPCHK_RULES_HTTP_CHK)
3001 goto bad_tcp_kw;
3002 if (type != TCPCHK_EXPECT_UNDEF) {
3003 memprintf(errmsg, "only on pattern expected");
3004 goto error;
3005 }
3006 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3007
3008 if (!*(args[cur_arg+1])) {
3009 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3010 goto error;
3011 }
3012 cur_arg++;
3013 pattern = args[cur_arg];
3014 }
3015 else if (strcmp(args[cur_arg], "custom") == 0) {
3016 if (in_pattern) {
3017 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3018 goto error;
3019 }
3020 if (type != TCPCHK_EXPECT_UNDEF) {
3021 memprintf(errmsg, "only on pattern expected");
3022 goto error;
3023 }
3024 type = TCPCHK_EXPECT_CUSTOM;
3025 }
3026 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3027 int orig_arg = cur_arg;
3028
3029 if (proto != TCPCHK_RULES_HTTP_CHK)
3030 goto bad_tcp_kw;
3031 if (type != TCPCHK_EXPECT_UNDEF) {
3032 memprintf(errmsg, "only on pattern expected");
3033 goto error;
3034 }
3035 type = TCPCHK_EXPECT_HTTP_HEADER;
3036
3037 if (strcmp(args[cur_arg], "fhdr") == 0)
3038 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3039
3040 /* Parse the name pattern, mandatory */
3041 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3042 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3043 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3044 args[orig_arg]);
3045 goto error;
3046 }
3047
3048 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3049 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3050
3051 cur_arg += 2;
3052 if (strcmp(args[cur_arg], "-m") == 0) {
3053 if (!*(args[cur_arg+1])) {
3054 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3055 args[orig_arg], args[cur_arg]);
3056 goto error;
3057 }
3058 if (strcmp(args[cur_arg+1], "str") == 0)
3059 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3060 else if (strcmp(args[cur_arg+1], "beg") == 0)
3061 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3062 else if (strcmp(args[cur_arg+1], "end") == 0)
3063 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3064 else if (strcmp(args[cur_arg+1], "sub") == 0)
3065 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3066 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3067 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3068 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3069 args[orig_arg]);
3070 goto error;
3071 }
3072 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3073 }
3074 else {
3075 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3076 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3077 goto error;
3078 }
3079 cur_arg += 2;
3080 }
3081 else
3082 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3083 npat = args[cur_arg];
3084
3085 if (!*(args[cur_arg+1]) ||
3086 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3087 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3088 goto next;
3089 }
3090 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3091 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3092
3093 /* Parse the value pattern, optional */
3094 if (strcmp(args[cur_arg+2], "-m") == 0) {
3095 cur_arg += 2;
3096 if (!*(args[cur_arg+1])) {
3097 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3098 args[orig_arg], args[cur_arg]);
3099 goto error;
3100 }
3101 if (strcmp(args[cur_arg+1], "str") == 0)
3102 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3103 else if (strcmp(args[cur_arg+1], "beg") == 0)
3104 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3105 else if (strcmp(args[cur_arg+1], "end") == 0)
3106 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3107 else if (strcmp(args[cur_arg+1], "sub") == 0)
3108 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3109 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3110 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3111 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3112 args[orig_arg]);
3113 goto error;
3114 }
3115 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3116 }
3117 else {
3118 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3119 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3120 goto error;
3121 }
3122 }
3123 else
3124 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3125
3126 if (!*(args[cur_arg+2])) {
3127 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3128 goto error;
3129 }
3130 vpat = args[cur_arg+2];
3131 cur_arg += 2;
3132 }
3133 else if (strcmp(args[cur_arg], "comment") == 0) {
3134 if (in_pattern) {
3135 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3136 goto error;
3137 }
3138 if (!*(args[cur_arg+1])) {
3139 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3140 goto error;
3141 }
3142 cur_arg++;
3143 free(comment);
3144 comment = strdup(args[cur_arg]);
3145 if (!comment) {
3146 memprintf(errmsg, "out of memory");
3147 goto error;
3148 }
3149 }
3150 else if (strcmp(args[cur_arg], "on-success") == 0) {
3151 if (in_pattern) {
3152 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3153 goto error;
3154 }
3155 if (!*(args[cur_arg+1])) {
3156 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3157 goto error;
3158 }
3159 cur_arg++;
3160 on_success_msg = args[cur_arg];
3161 }
3162 else if (strcmp(args[cur_arg], "on-error") == 0) {
3163 if (in_pattern) {
3164 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3165 goto error;
3166 }
3167 if (!*(args[cur_arg+1])) {
3168 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3169 goto error;
3170 }
3171 cur_arg++;
3172 on_error_msg = args[cur_arg];
3173 }
3174 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3175 if (in_pattern) {
3176 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3177 goto error;
3178 }
3179 if (!*(args[cur_arg+1])) {
3180 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3181 goto error;
3182 }
3183 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3184 ok_st = HCHK_STATUS_L7OKD;
3185 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3186 ok_st = HCHK_STATUS_L7OKCD;
3187 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3188 ok_st = HCHK_STATUS_L6OK;
3189 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3190 ok_st = HCHK_STATUS_L4OK;
3191 else {
3192 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3193 args[cur_arg], args[cur_arg+1]);
3194 goto error;
3195 }
3196 cur_arg++;
3197 }
3198 else if (strcmp(args[cur_arg], "error-status") == 0) {
3199 if (in_pattern) {
3200 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3201 goto error;
3202 }
3203 if (!*(args[cur_arg+1])) {
3204 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3205 goto error;
3206 }
3207 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3208 err_st = HCHK_STATUS_L7RSP;
3209 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3210 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003211 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3212 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003213 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3214 err_st = HCHK_STATUS_L6RSP;
3215 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3216 err_st = HCHK_STATUS_L4CON;
3217 else {
3218 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3219 args[cur_arg], args[cur_arg+1]);
3220 goto error;
3221 }
3222 cur_arg++;
3223 }
3224 else if (strcmp(args[cur_arg], "status-code") == 0) {
3225 int idx = 0;
3226
3227 if (in_pattern) {
3228 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3229 goto error;
3230 }
3231 if (!*(args[cur_arg+1])) {
3232 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3233 goto error;
3234 }
3235
3236 cur_arg++;
3237 release_sample_expr(status_expr);
3238 px->conf.args.ctx = ARGC_SRV;
3239 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003240 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003241 if (!status_expr) {
3242 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3243 goto error;
3244 }
3245 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3246 memprintf(errmsg, "error detected while parsing status-code expression : "
3247 " fetch method '%s' extracts information from '%s', "
3248 "none of which is available here.\n",
3249 args[cur_arg], sample_src_names(status_expr->fetch->use));
3250 goto error;
3251 }
3252 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3253 }
3254 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3255 if (in_pattern) {
3256 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3257 goto error;
3258 }
3259 if (!*(args[cur_arg+1])) {
3260 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3261 goto error;
3262 }
3263 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3264 tout_st = HCHK_STATUS_L7TOUT;
3265 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3266 tout_st = HCHK_STATUS_L6TOUT;
3267 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3268 tout_st = HCHK_STATUS_L4TOUT;
3269 else {
3270 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3271 args[cur_arg], args[cur_arg+1]);
3272 goto error;
3273 }
3274 cur_arg++;
3275 }
3276 else {
3277 if (proto == TCPCHK_RULES_HTTP_CHK) {
3278 bad_http_kw:
3279 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3280 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3281 }
3282 else {
3283 bad_tcp_kw:
3284 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3285 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3286 }
3287 goto error;
3288 }
3289 next:
3290 cur_arg++;
3291 }
3292
3293 chk = calloc(1, sizeof(*chk));
3294 if (!chk) {
3295 memprintf(errmsg, "out of memory");
3296 goto error;
3297 }
3298 chk->action = TCPCHK_ACT_EXPECT;
3299 LIST_INIT(&chk->expect.onerror_fmt);
3300 LIST_INIT(&chk->expect.onsuccess_fmt);
3301 chk->comment = comment; comment = NULL;
3302 chk->expect.type = type;
3303 chk->expect.min_recv = min_recv;
3304 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3305 chk->expect.ok_status = ok_st;
3306 chk->expect.err_status = err_st;
3307 chk->expect.tout_status = tout_st;
3308 chk->expect.status_expr = status_expr; status_expr = NULL;
3309
3310 if (on_success_msg) {
3311 px->conf.args.ctx = ARGC_SRV;
3312 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3313 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3314 goto error;
3315 }
3316 }
3317 if (on_error_msg) {
3318 px->conf.args.ctx = ARGC_SRV;
3319 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3320 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3321 goto error;
3322 }
3323 }
3324
3325 switch (chk->expect.type) {
3326 case TCPCHK_EXPECT_HTTP_STATUS: {
3327 const char *p = pattern;
3328 unsigned int c1,c2;
3329
3330 chk->expect.codes.codes = NULL;
3331 chk->expect.codes.num = 0;
3332 while (1) {
3333 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3334 if (*p == '-') {
3335 p++;
3336 c2 = read_uint(&p, pattern + strlen(pattern));
3337 }
3338 if (c1 > c2) {
3339 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3340 goto error;
3341 }
3342
3343 chk->expect.codes.num++;
3344 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3345 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3346 if (!chk->expect.codes.codes) {
3347 memprintf(errmsg, "out of memory");
3348 goto error;
3349 }
3350 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3351 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3352
3353 if (*p == '\0')
3354 break;
3355 if (*p != ',') {
3356 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3357 goto error;
3358 }
3359 p++;
3360 }
3361 break;
3362 }
3363 case TCPCHK_EXPECT_STRING:
3364 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003365 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003366 if (!isttest(chk->expect.data)) {
3367 memprintf(errmsg, "out of memory");
3368 goto error;
3369 }
3370 break;
3371 case TCPCHK_EXPECT_BINARY: {
3372 int len = chk->expect.data.len;
3373
3374 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3375 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3376 goto error;
3377 }
3378 chk->expect.data.len = len;
3379 break;
3380 }
3381 case TCPCHK_EXPECT_STRING_REGEX:
3382 case TCPCHK_EXPECT_BINARY_REGEX:
3383 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3384 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3385 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3386 if (!chk->expect.regex)
3387 goto error;
3388 break;
3389
3390 case TCPCHK_EXPECT_STRING_LF:
3391 case TCPCHK_EXPECT_BINARY_LF:
3392 case TCPCHK_EXPECT_HTTP_BODY_LF:
3393 LIST_INIT(&chk->expect.fmt);
3394 px->conf.args.ctx = ARGC_SRV;
3395 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3396 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3397 goto error;
3398 }
3399 break;
3400
3401 case TCPCHK_EXPECT_HTTP_HEADER:
3402 if (!npat) {
3403 memprintf(errmsg, "unexpected error, undefined header name pattern");
3404 goto error;
3405 }
3406 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3407 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3408 if (!chk->expect.hdr.name_re)
3409 goto error;
3410 }
3411 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3412 px->conf.args.ctx = ARGC_SRV;
3413 LIST_INIT(&chk->expect.hdr.name_fmt);
3414 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3415 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3416 goto error;
3417 }
3418 }
3419 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003420 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003421 if (!isttest(chk->expect.hdr.name)) {
3422 memprintf(errmsg, "out of memory");
3423 goto error;
3424 }
3425 }
3426
3427 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3428 chk->expect.hdr.value = IST_NULL;
3429 break;
3430 }
3431
3432 if (!vpat) {
3433 memprintf(errmsg, "unexpected error, undefined header value pattern");
3434 goto error;
3435 }
3436 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3437 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3438 if (!chk->expect.hdr.value_re)
3439 goto error;
3440 }
3441 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3442 px->conf.args.ctx = ARGC_SRV;
3443 LIST_INIT(&chk->expect.hdr.value_fmt);
3444 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3445 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3446 goto error;
3447 }
3448 }
3449 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003450 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003451 if (!isttest(chk->expect.hdr.value)) {
3452 memprintf(errmsg, "out of memory");
3453 goto error;
3454 }
3455 }
3456
3457 break;
3458 case TCPCHK_EXPECT_CUSTOM:
3459 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3460 break;
3461 case TCPCHK_EXPECT_UNDEF:
3462 memprintf(errmsg, "pattern not found");
3463 goto error;
3464 }
3465
3466 /* All tcp-check expect points back to the first inverse expect rule in
3467 * a chain of one or more expect rule, potentially itself.
3468 */
3469 chk->expect.head = chk;
3470 list_for_each_entry_rev(prev_check, rules, list) {
3471 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3472 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3473 chk->expect.head = prev_check;
3474 continue;
3475 }
3476 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3477 break;
3478 }
3479 return chk;
3480
3481 error:
3482 free_tcpcheck(chk, 0);
3483 free(comment);
3484 release_sample_expr(status_expr);
3485 return NULL;
3486}
3487
3488/* Overwrites fields of the old http send rule with those of the new one. When
3489 * replaced, old values are freed and replaced by the new ones. New values are
3490 * not copied but transferred. At the end <new> should be empty and can be
3491 * safely released. This function never fails.
3492 */
3493void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3494{
3495 struct logformat_node *lf, *lfb;
3496 struct tcpcheck_http_hdr *hdr, *bhdr;
3497
3498
3499 if (new->send.http.meth.str.area) {
3500 free(old->send.http.meth.str.area);
3501 old->send.http.meth.meth = new->send.http.meth.meth;
3502 old->send.http.meth.str.area = new->send.http.meth.str.area;
3503 old->send.http.meth.str.data = new->send.http.meth.str.data;
3504 new->send.http.meth.str = BUF_NULL;
3505 }
3506
3507 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3508 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3509 istfree(&old->send.http.uri);
3510 else
3511 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3512 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3513 old->send.http.uri = new->send.http.uri;
3514 new->send.http.uri = IST_NULL;
3515 }
3516 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3517 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3518 istfree(&old->send.http.uri);
3519 else
3520 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3521 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3522 LIST_INIT(&old->send.http.uri_fmt);
3523 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003524 LIST_DELETE(&lf->list);
3525 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003526 }
3527 }
3528
3529 if (isttest(new->send.http.vsn)) {
3530 istfree(&old->send.http.vsn);
3531 old->send.http.vsn = new->send.http.vsn;
3532 new->send.http.vsn = IST_NULL;
3533 }
3534
3535 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3536 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003537 LIST_DELETE(&hdr->list);
3538 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003539 }
3540
3541 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3542 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3543 istfree(&old->send.http.body);
3544 else
3545 free_tcpcheck_fmt(&old->send.http.body_fmt);
3546 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3547 old->send.http.body = new->send.http.body;
3548 new->send.http.body = IST_NULL;
3549 }
3550 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3551 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3552 istfree(&old->send.http.body);
3553 else
3554 free_tcpcheck_fmt(&old->send.http.body_fmt);
3555 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3556 LIST_INIT(&old->send.http.body_fmt);
3557 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003558 LIST_DELETE(&lf->list);
3559 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003560 }
3561 }
3562}
3563
3564/* Internal function used to add an http-check rule in a list during the config
3565 * parsing step. Depending on its type, and the previously inserted rules, a
3566 * specific action may be performed or an error may be reported. This functions
3567 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3568 * message.
3569 */
3570int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3571{
3572 struct tcpcheck_rule *r;
3573
3574 /* the implicit send rule coming from an "option httpchk" line must be
3575 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003576 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003577 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003578 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003579 * sure the ruleset remains valid.
3580 */
3581
3582 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3583 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3584 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3585 * following tests are performed :
3586 *
3587 * 1- If there is no such rule or if it is not a send rule, the implicit send
3588 * rule is pushed in front of the ruleset
3589 *
3590 * 2- If it is another implicit send rule, it is replaced with the new one.
3591 *
3592 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3593 * both, overwriting the old send rule (the explicit one) with info of the
3594 * new send rule (the implicit one).
3595 */
3596 r = get_first_tcpcheck_rule(rules);
3597 if (r && r->action == TCPCHK_ACT_CONNECT)
3598 r = get_next_tcpcheck_rule(rules, r);
3599 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003600 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003601 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003602 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003603 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003604 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003605 }
3606 else {
3607 tcpcheck_overwrite_send_http_rule(r, chk);
3608 free_tcpcheck(chk, 0);
3609 }
3610 }
3611 else {
3612 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3613 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3614 * with an existing implicit send rule, if any. At the end, if there is no error,
3615 * the rule is appended to the list.
3616 */
3617
3618 r = get_last_tcpcheck_rule(rules);
3619 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3620 /* no error */;
3621 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3622 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3623 chk->index+1);
3624 return 0;
3625 }
3626 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3627 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3628 chk->index+1);
3629 return 0;
3630 }
3631 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3632 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3633 chk->index+1);
3634 return 0;
3635 }
3636
3637 if (chk->action == TCPCHK_ACT_SEND) {
3638 r = get_first_tcpcheck_rule(rules);
3639 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3640 tcpcheck_overwrite_send_http_rule(r, chk);
3641 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003642 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003643 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3644 chk = r;
3645 }
3646 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003647 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003648 }
3649 return 1;
3650}
3651
3652/* Check tcp-check health-check configuration for the proxy <px>. */
3653static int check_proxy_tcpcheck(struct proxy *px)
3654{
3655 struct tcpcheck_rule *chk, *back;
3656 char *comment = NULL, *errmsg = NULL;
3657 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003658 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003659
3660 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3661 deinit_proxy_tcpcheck(px);
3662 goto out;
3663 }
3664
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003665 ha_free(&px->check_command);
3666 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003667
3668 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003669 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003670 ret |= ERR_ALERT | ERR_FATAL;
3671 goto out;
3672 }
3673
3674 /* HTTP ruleset only : */
3675 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3676 struct tcpcheck_rule *next;
3677
3678 /* move remaining implicit send rule from "option httpchk" line to the right place.
3679 * If such rule exists, it must be the first one. In this case, the rule is moved
3680 * after the first connect rule, if any. Otherwise, nothing is done.
3681 */
3682 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3683 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3684 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3685 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003686 LIST_DELETE(&chk->list);
3687 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003688 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003689 }
3690 }
3691
3692 /* add implicit expect rule if the last one is a send. It is inherited from previous
3693 * versions where the http expect rule was optional. Now it is possible to chained
3694 * send/expect rules but the last expect may still be implicit.
3695 */
3696 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3697 if (chk && chk->action == TCPCHK_ACT_SEND) {
3698 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3699 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3700 px->conf.file, px->conf.line, &errmsg);
3701 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003702 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003703 "(%s).\n", px->id, errmsg);
3704 free(errmsg);
3705 ret |= ERR_ALERT | ERR_FATAL;
3706 goto out;
3707 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003708 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003709 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003710 }
3711 }
3712
3713 /* For all ruleset: */
3714
3715 /* If there is no connect rule preceding all send / expect rules, an
3716 * implicit one is inserted before all others.
3717 */
3718 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3719 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3720 chk = calloc(1, sizeof(*chk));
3721 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003722 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003723 "(out of memory).\n", px->id);
3724 ret |= ERR_ALERT | ERR_FATAL;
3725 goto out;
3726 }
3727 chk->action = TCPCHK_ACT_CONNECT;
3728 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003729 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003730 }
3731
3732 /* Remove all comment rules. To do so, when a such rule is found, the
3733 * comment is assigned to the following rule(s).
3734 */
3735 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003736 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3737 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003738
3739 prev_action = chk->action;
3740 switch (chk->action) {
3741 case TCPCHK_ACT_COMMENT:
3742 free(comment);
3743 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003744 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003745 free(chk);
3746 break;
3747 case TCPCHK_ACT_CONNECT:
3748 if (!chk->comment && comment)
3749 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003750 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003751 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003752 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003753 break;
3754 case TCPCHK_ACT_SEND:
3755 case TCPCHK_ACT_EXPECT:
3756 if (!chk->comment && comment)
3757 chk->comment = strdup(comment);
3758 break;
3759 }
3760 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003761 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003762
3763 out:
3764 return ret;
3765}
3766
3767void deinit_proxy_tcpcheck(struct proxy *px)
3768{
3769 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3770 px->tcpcheck_rules.flags = 0;
3771 px->tcpcheck_rules.list = NULL;
3772}
3773
3774static void deinit_tcpchecks()
3775{
3776 struct tcpcheck_ruleset *rs;
3777 struct tcpcheck_rule *r, *rb;
3778 struct ebpt_node *node, *next;
3779
3780 node = ebpt_first(&shared_tcpchecks);
3781 while (node) {
3782 next = ebpt_next(node);
3783 ebpt_delete(node);
3784 free(node->key);
3785 rs = container_of(node, typeof(*rs), node);
3786 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003787 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003788 free_tcpcheck(r, 0);
3789 }
3790 free(rs);
3791 node = next;
3792 }
3793}
3794
3795int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3796{
3797 struct tcpcheck_rule *tcpcheck, *prev_check;
3798 struct tcpcheck_expect *expect;
3799
Willy Tarreau6922e552021-03-22 21:11:45 +01003800 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003801 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003802 tcpcheck->action = TCPCHK_ACT_EXPECT;
3803
3804 expect = &tcpcheck->expect;
3805 expect->type = TCPCHK_EXPECT_STRING;
3806 LIST_INIT(&expect->onerror_fmt);
3807 LIST_INIT(&expect->onsuccess_fmt);
3808 expect->ok_status = HCHK_STATUS_L7OKD;
3809 expect->err_status = HCHK_STATUS_L7RSP;
3810 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003811 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003812 if (!isttest(expect->data)) {
3813 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3814 return 0;
3815 }
3816
3817 /* All tcp-check expect points back to the first inverse expect rule
3818 * in a chain of one or more expect rule, potentially itself.
3819 */
3820 tcpcheck->expect.head = tcpcheck;
3821 list_for_each_entry_rev(prev_check, rules->list, list) {
3822 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3823 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3824 tcpcheck->expect.head = prev_check;
3825 continue;
3826 }
3827 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3828 break;
3829 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003830 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003831 return 1;
3832}
3833
3834int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3835{
3836 struct tcpcheck_rule *tcpcheck;
3837 struct tcpcheck_send *send;
3838 const char *in;
3839 char *dst;
3840 int i;
3841
Willy Tarreau6922e552021-03-22 21:11:45 +01003842 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003843 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003844 tcpcheck->action = TCPCHK_ACT_SEND;
3845
3846 send = &tcpcheck->send;
3847 send->type = TCPCHK_SEND_STRING;
3848
3849 for (i = 0; strs[i]; i++)
3850 send->data.len += strlen(strs[i]);
3851
3852 send->data.ptr = malloc(istlen(send->data) + 1);
3853 if (!isttest(send->data)) {
3854 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3855 return 0;
3856 }
3857
3858 dst = istptr(send->data);
3859 for (i = 0; strs[i]; i++)
3860 for (in = strs[i]; (*dst = *in++); dst++);
3861 *dst = 0;
3862
Willy Tarreau2b718102021-04-21 07:32:39 +02003863 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003864 return 1;
3865}
3866
3867/* Parses the "tcp-check" proxy keyword */
3868static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003869 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003870 char **errmsg)
3871{
3872 struct tcpcheck_ruleset *rs = NULL;
3873 struct tcpcheck_rule *chk = NULL;
3874 int index, cur_arg, ret = 0;
3875
3876 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3877 ret = 1;
3878
3879 /* Deduce the ruleset name from the proxy info */
3880 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3881 ((curpx == defpx) ? "defaults" : curpx->id),
3882 curpx->conf.file, curpx->conf.line);
3883
3884 rs = find_tcpcheck_ruleset(b_orig(&trash));
3885 if (rs == NULL) {
3886 rs = create_tcpcheck_ruleset(b_orig(&trash));
3887 if (rs == NULL) {
3888 memprintf(errmsg, "out of memory.\n");
3889 goto error;
3890 }
3891 }
3892
3893 index = 0;
3894 if (!LIST_ISEMPTY(&rs->rules)) {
3895 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3896 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003897 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003898 }
3899
3900 cur_arg = 1;
3901 if (strcmp(args[cur_arg], "connect") == 0)
3902 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3903 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3904 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3905 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3906 else if (strcmp(args[cur_arg], "expect") == 0)
3907 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3908 else if (strcmp(args[cur_arg], "comment") == 0)
3909 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3910 else {
3911 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3912
3913 if (!kw) {
3914 action_kw_tcp_check_build_list(&trash);
3915 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3916 "%s%s. but got '%s'",
3917 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3918 goto error;
3919 }
3920 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3921 }
3922
3923 if (!chk) {
3924 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3925 goto error;
3926 }
3927 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3928
3929 /* No error: add the tcp-check rule in the list */
3930 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003931 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003932
3933 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3934 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3935 /* Use this ruleset if the proxy already has tcp-check enabled */
3936 curpx->tcpcheck_rules.list = &rs->rules;
3937 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3938 }
3939 else {
3940 /* mark this ruleset as unused for now */
3941 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3942 }
3943
3944 return ret;
3945
3946 error:
3947 free_tcpcheck(chk, 0);
3948 free_tcpcheck_ruleset(rs);
3949 return -1;
3950}
3951
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003952/* Parses the "http-check" proxy keyword */
3953static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003954 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003955 char **errmsg)
3956{
3957 struct tcpcheck_ruleset *rs = NULL;
3958 struct tcpcheck_rule *chk = NULL;
3959 int index, cur_arg, ret = 0;
3960
3961 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3962 ret = 1;
3963
3964 cur_arg = 1;
3965 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3966 /* enable a graceful server shutdown on an HTTP 404 response */
3967 curpx->options |= PR_O_DISABLE404;
3968 if (too_many_args(1, args, errmsg, NULL))
3969 goto error;
3970 goto out;
3971 }
3972 else if (strcmp(args[cur_arg], "send-state") == 0) {
3973 /* enable emission of the apparent state of a server in HTTP checks */
3974 curpx->options2 |= PR_O2_CHK_SNDST;
3975 if (too_many_args(1, args, errmsg, NULL))
3976 goto error;
3977 goto out;
3978 }
3979
3980 /* Deduce the ruleset name from the proxy info */
3981 chunk_printf(&trash, "*http-check-%s_%s-%d",
3982 ((curpx == defpx) ? "defaults" : curpx->id),
3983 curpx->conf.file, curpx->conf.line);
3984
3985 rs = find_tcpcheck_ruleset(b_orig(&trash));
3986 if (rs == NULL) {
3987 rs = create_tcpcheck_ruleset(b_orig(&trash));
3988 if (rs == NULL) {
3989 memprintf(errmsg, "out of memory.\n");
3990 goto error;
3991 }
3992 }
3993
3994 index = 0;
3995 if (!LIST_ISEMPTY(&rs->rules)) {
3996 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3997 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
3998 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003999 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004000 }
4001
4002 if (strcmp(args[cur_arg], "connect") == 0)
4003 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4004 else if (strcmp(args[cur_arg], "send") == 0)
4005 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4006 else if (strcmp(args[cur_arg], "expect") == 0)
4007 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4008 file, line, errmsg);
4009 else if (strcmp(args[cur_arg], "comment") == 0)
4010 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4011 else {
4012 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4013
4014 if (!kw) {
4015 action_kw_tcp_check_build_list(&trash);
4016 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4017 " 'send', 'expect'%s%s. but got '%s'",
4018 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4019 goto error;
4020 }
4021 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4022 }
4023
4024 if (!chk) {
4025 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4026 goto error;
4027 }
4028 ret = (*errmsg != NULL); /* Handle warning */
4029
4030 chk->index = index;
4031 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4032 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4033 /* Use this ruleset if the proxy already has http-check enabled */
4034 curpx->tcpcheck_rules.list = &rs->rules;
4035 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4036 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4037 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4038 curpx->tcpcheck_rules.list = NULL;
4039 goto error;
4040 }
4041 }
4042 else {
4043 /* mark this ruleset as unused for now */
4044 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004045 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004046 }
4047
4048 out:
4049 return ret;
4050
4051 error:
4052 free_tcpcheck(chk, 0);
4053 free_tcpcheck_ruleset(rs);
4054 return -1;
4055}
4056
4057/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004058int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004059 const char *file, int line)
4060{
4061 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4062 static char *redis_res = "+PONG\r\n";
4063
4064 struct tcpcheck_ruleset *rs = NULL;
4065 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4066 struct tcpcheck_rule *chk;
4067 char *errmsg = NULL;
4068 int err_code = 0;
4069
4070 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4071 err_code |= ERR_WARN;
4072
4073 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4074 goto out;
4075
4076 curpx->options2 &= ~PR_O2_CHK_ANY;
4077 curpx->options2 |= PR_O2_TCPCHK_CHK;
4078
4079 free_tcpcheck_vars(&rules->preset_vars);
4080 rules->list = NULL;
4081 rules->flags = 0;
4082
4083 rs = find_tcpcheck_ruleset("*redis-check");
4084 if (rs)
4085 goto ruleset_found;
4086
4087 rs = create_tcpcheck_ruleset("*redis-check");
4088 if (rs == NULL) {
4089 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4090 goto error;
4091 }
4092
4093 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4094 1, curpx, &rs->rules, file, line, &errmsg);
4095 if (!chk) {
4096 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4097 goto error;
4098 }
4099 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004100 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004101
4102 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4103 "error-status", "L7STS",
4104 "on-error", "%[res.payload(0,0),cut_crlf]",
4105 "on-success", "Redis server is ok",
4106 ""},
4107 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4108 if (!chk) {
4109 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4110 goto error;
4111 }
4112 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004113 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004114
4115 ruleset_found:
4116 rules->list = &rs->rules;
4117 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4118 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4119
4120 out:
4121 free(errmsg);
4122 return err_code;
4123
4124 error:
4125 free_tcpcheck_ruleset(rs);
4126 err_code |= ERR_ALERT | ERR_FATAL;
4127 goto out;
4128}
4129
4130
4131/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004132int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004133 const char *file, int line)
4134{
4135 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4136 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4137 *
4138 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4139 */
4140 static char sslv3_client_hello[] = {
4141 "16" /* ContentType : 0x16 = Handshake */
4142 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4143 "0079" /* ContentLength : 0x79 bytes after this one */
4144 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4145 "000075" /* HandshakeLength : 0x75 bytes after this one */
4146 "0300" /* Hello Version : 0x0300 = v3 */
4147 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4148 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4149 "00" /* Session ID length : empty (no session ID) */
4150 "004E" /* Cipher Suite Length : 78 bytes after this one */
4151 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4152 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4153 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4154 "000D" "000E" "000F" "0010" /* various bit lengths, */
4155 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4156 "0015" "0016" "0017" "0018"
4157 "0019" "001A" "001B" "002F"
4158 "0030" "0031" "0032" "0033"
4159 "0034" "0035" "0036" "0037"
4160 "0038" "0039" "003A"
4161 "01" /* Compression Length : 0x01 = 1 byte for types */
4162 "00" /* Compression Type : 0x00 = NULL compression */
4163 };
4164
4165 struct tcpcheck_ruleset *rs = NULL;
4166 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4167 struct tcpcheck_rule *chk;
4168 char *errmsg = NULL;
4169 int err_code = 0;
4170
4171 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4172 err_code |= ERR_WARN;
4173
4174 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4175 goto out;
4176
4177 curpx->options2 &= ~PR_O2_CHK_ANY;
4178 curpx->options2 |= PR_O2_TCPCHK_CHK;
4179
4180 free_tcpcheck_vars(&rules->preset_vars);
4181 rules->list = NULL;
4182 rules->flags = 0;
4183
4184 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4185 if (rs)
4186 goto ruleset_found;
4187
4188 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4189 if (rs == NULL) {
4190 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4191 goto error;
4192 }
4193
4194 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4195 1, curpx, &rs->rules, file, line, &errmsg);
4196 if (!chk) {
4197 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4198 goto error;
4199 }
4200 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004201 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004202
4203 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4204 "min-recv", "5", "ok-status", "L6OK",
4205 "error-status", "L6RSP", "tout-status", "L6TOUT",
4206 ""},
4207 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4208 if (!chk) {
4209 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4210 goto error;
4211 }
4212 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004213 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004214
4215 ruleset_found:
4216 rules->list = &rs->rules;
4217 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4218 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4219
4220 out:
4221 free(errmsg);
4222 return err_code;
4223
4224 error:
4225 free_tcpcheck_ruleset(rs);
4226 err_code |= ERR_ALERT | ERR_FATAL;
4227 goto out;
4228}
4229
4230/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004231int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004232 const char *file, int line)
4233{
4234 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4235
4236 struct tcpcheck_ruleset *rs = NULL;
4237 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4238 struct tcpcheck_rule *chk;
4239 struct tcpcheck_var *var = NULL;
4240 char *cmd = NULL, *errmsg = NULL;
4241 int err_code = 0;
4242
4243 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4244 err_code |= ERR_WARN;
4245
4246 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4247 goto out;
4248
4249 curpx->options2 &= ~PR_O2_CHK_ANY;
4250 curpx->options2 |= PR_O2_TCPCHK_CHK;
4251
4252 free_tcpcheck_vars(&rules->preset_vars);
4253 rules->list = NULL;
4254 rules->flags = 0;
4255
4256 cur_arg += 2;
4257 if (*args[cur_arg] && *args[cur_arg+1] &&
4258 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4259 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4260 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4261 if (cmd)
4262 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4263 }
4264 else {
4265 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4266 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4267 cmd = strdup("HELO localhost");
4268 }
4269
4270 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4271 if (cmd == NULL || var == NULL) {
4272 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4273 goto error;
4274 }
4275 var->data.type = SMP_T_STR;
4276 var->data.u.str.area = cmd;
4277 var->data.u.str.data = strlen(cmd);
4278 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004279 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004280 cmd = NULL;
4281 var = NULL;
4282
4283 rs = find_tcpcheck_ruleset("*smtp-check");
4284 if (rs)
4285 goto ruleset_found;
4286
4287 rs = create_tcpcheck_ruleset("*smtp-check");
4288 if (rs == NULL) {
4289 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4290 goto error;
4291 }
4292
4293 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4294 1, curpx, &rs->rules, file, line, &errmsg);
4295 if (!chk) {
4296 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4297 goto error;
4298 }
4299 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004300 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004301
4302 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4303 "min-recv", "4",
4304 "error-status", "L7RSP",
4305 "on-error", "%[res.payload(0,0),cut_crlf]",
4306 ""},
4307 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4308 if (!chk) {
4309 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4310 goto error;
4311 }
4312 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004313 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004314
4315 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4316 "min-recv", "4",
4317 "error-status", "L7STS",
4318 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4319 "status-code", "res.payload(0,3)",
4320 ""},
4321 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4322 if (!chk) {
4323 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4324 goto error;
4325 }
4326 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004327 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004328
4329 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4330 1, curpx, &rs->rules, file, line, &errmsg);
4331 if (!chk) {
4332 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4333 goto error;
4334 }
4335 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004336 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004337
4338 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4339 "min-recv", "4",
4340 "error-status", "L7STS",
4341 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4342 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4343 "status-code", "res.payload(0,3)",
4344 ""},
4345 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4346 if (!chk) {
4347 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4348 goto error;
4349 }
4350 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004351 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004352
4353 ruleset_found:
4354 rules->list = &rs->rules;
4355 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4356 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4357
4358 out:
4359 free(errmsg);
4360 return err_code;
4361
4362 error:
4363 free(cmd);
4364 free(var);
4365 free_tcpcheck_vars(&rules->preset_vars);
4366 free_tcpcheck_ruleset(rs);
4367 err_code |= ERR_ALERT | ERR_FATAL;
4368 goto out;
4369}
4370
4371/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004372int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004373 const char *file, int line)
4374{
4375 static char pgsql_req[] = {
4376 "%[var(check.plen),htonl,hex]" /* The packet length*/
4377 "00030000" /* the version 3.0 */
4378 "7573657200" /* "user" key */
4379 "%[var(check.username),hex]00" /* the username */
4380 "00"
4381 };
4382
4383 struct tcpcheck_ruleset *rs = NULL;
4384 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4385 struct tcpcheck_rule *chk;
4386 struct tcpcheck_var *var = NULL;
4387 char *user = NULL, *errmsg = NULL;
4388 size_t packetlen = 0;
4389 int err_code = 0;
4390
4391 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4392 err_code |= ERR_WARN;
4393
4394 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4395 goto out;
4396
4397 curpx->options2 &= ~PR_O2_CHK_ANY;
4398 curpx->options2 |= PR_O2_TCPCHK_CHK;
4399
4400 free_tcpcheck_vars(&rules->preset_vars);
4401 rules->list = NULL;
4402 rules->flags = 0;
4403
4404 cur_arg += 2;
4405 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4406 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4407 file, line, args[0], args[1]);
4408 goto error;
4409 }
4410 if (strcmp(args[cur_arg], "user") == 0) {
4411 packetlen = 15 + strlen(args[cur_arg+1]);
4412 user = strdup(args[cur_arg+1]);
4413
4414 var = create_tcpcheck_var(ist("check.username"));
4415 if (user == NULL || var == NULL) {
4416 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4417 goto error;
4418 }
4419 var->data.type = SMP_T_STR;
4420 var->data.u.str.area = user;
4421 var->data.u.str.data = strlen(user);
4422 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004423 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004424 user = NULL;
4425 var = NULL;
4426
4427 var = create_tcpcheck_var(ist("check.plen"));
4428 if (var == NULL) {
4429 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4430 goto error;
4431 }
4432 var->data.type = SMP_T_SINT;
4433 var->data.u.sint = packetlen;
4434 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004435 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004436 var = NULL;
4437 }
4438 else {
4439 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4440 file, line, args[0], args[1]);
4441 goto error;
4442 }
4443
4444 rs = find_tcpcheck_ruleset("*pgsql-check");
4445 if (rs)
4446 goto ruleset_found;
4447
4448 rs = create_tcpcheck_ruleset("*pgsql-check");
4449 if (rs == NULL) {
4450 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4451 goto error;
4452 }
4453
4454 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4455 1, curpx, &rs->rules, file, line, &errmsg);
4456 if (!chk) {
4457 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4458 goto error;
4459 }
4460 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004461 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004462
4463 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4464 1, curpx, &rs->rules, file, line, &errmsg);
4465 if (!chk) {
4466 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4467 goto error;
4468 }
4469 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004470 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004471
4472 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4473 "min-recv", "5",
4474 "error-status", "L7RSP",
4475 "on-error", "%[res.payload(6,0)]",
4476 ""},
4477 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4478 if (!chk) {
4479 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4480 goto error;
4481 }
4482 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004483 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004484
4485 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4486 "min-recv", "9",
4487 "error-status", "L7STS",
4488 "on-success", "PostgreSQL server is ok",
4489 "on-error", "PostgreSQL unknown error",
4490 ""},
4491 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4492 if (!chk) {
4493 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4494 goto error;
4495 }
4496 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004497 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004498
4499 ruleset_found:
4500 rules->list = &rs->rules;
4501 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4502 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4503
4504 out:
4505 free(errmsg);
4506 return err_code;
4507
4508 error:
4509 free(user);
4510 free(var);
4511 free_tcpcheck_vars(&rules->preset_vars);
4512 free_tcpcheck_ruleset(rs);
4513 err_code |= ERR_ALERT | ERR_FATAL;
4514 goto out;
4515}
4516
4517
4518/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004519int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004520 const char *file, int line)
4521{
4522 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4523 * const char mysql40_client_auth_pkt[] = {
4524 * "\x0e\x00\x00" // packet length
4525 * "\x01" // packet number
4526 * "\x00\x00" // client capabilities
4527 * "\x00\x00\x01" // max packet
4528 * "haproxy\x00" // username (null terminated string)
4529 * "\x00" // filler (always 0x00)
4530 * "\x01\x00\x00" // packet length
4531 * "\x00" // packet number
4532 * "\x01" // COM_QUIT command
4533 * };
4534 */
4535 static char mysql40_rsname[] = "*mysql40-check";
4536 static char mysql40_req[] = {
4537 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4538 "0080" /* client capabilities */
4539 "000001" /* max packet */
4540 "%[var(check.username),hex]00" /* the username */
4541 "00" /* filler (always 0x00) */
4542 "010000" /* packet length*/
4543 "00" /* sequence ID */
4544 "01" /* COM_QUIT command */
4545 };
4546
4547 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4548 * const char mysql41_client_auth_pkt[] = {
4549 * "\x0e\x00\x00\" // packet length
4550 * "\x01" // packet number
4551 * "\x00\x00\x00\x00" // client capabilities
4552 * "\x00\x00\x00\x01" // max packet
4553 * "\x21" // character set (UTF-8)
4554 * char[23] // All zeroes
4555 * "haproxy\x00" // username (null terminated string)
4556 * "\x00" // filler (always 0x00)
4557 * "\x01\x00\x00" // packet length
4558 * "\x00" // packet number
4559 * "\x01" // COM_QUIT command
4560 * };
4561 */
4562 static char mysql41_rsname[] = "*mysql41-check";
4563 static char mysql41_req[] = {
4564 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4565 "00820000" /* client capabilities */
4566 "00800001" /* max packet */
4567 "21" /* character set (UTF-8) */
4568 "000000000000000000000000" /* 23 bytes, al zeroes */
4569 "0000000000000000000000"
4570 "%[var(check.username),hex]00" /* the username */
4571 "00" /* filler (always 0x00) */
4572 "010000" /* packet length*/
4573 "00" /* sequence ID */
4574 "01" /* COM_QUIT command */
4575 };
4576
4577 struct tcpcheck_ruleset *rs = NULL;
4578 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4579 struct tcpcheck_rule *chk;
4580 struct tcpcheck_var *var = NULL;
4581 char *mysql_rsname = "*mysql-check";
4582 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4583 int index = 0, err_code = 0;
4584
4585 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4586 err_code |= ERR_WARN;
4587
4588 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4589 goto out;
4590
4591 curpx->options2 &= ~PR_O2_CHK_ANY;
4592 curpx->options2 |= PR_O2_TCPCHK_CHK;
4593
4594 free_tcpcheck_vars(&rules->preset_vars);
4595 rules->list = NULL;
4596 rules->flags = 0;
4597
4598 cur_arg += 2;
4599 if (*args[cur_arg]) {
4600 int packetlen, userlen;
4601
4602 if (strcmp(args[cur_arg], "user") != 0) {
4603 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4604 file, line, args[0], args[1], args[cur_arg]);
4605 goto error;
4606 }
4607
4608 if (*(args[cur_arg+1]) == 0) {
4609 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4610 file, line, args[0], args[1], args[cur_arg]);
4611 goto error;
4612 }
4613
4614 hdr = calloc(4, sizeof(*hdr));
4615 user = strdup(args[cur_arg+1]);
4616 userlen = strlen(args[cur_arg+1]);
4617
4618 if (hdr == NULL || user == NULL) {
4619 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4620 goto error;
4621 }
4622
4623 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4624 packetlen = userlen + 7 + 27;
4625 mysql_req = mysql41_req;
4626 mysql_rsname = mysql41_rsname;
4627 }
4628 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4629 packetlen = userlen + 7;
4630 mysql_req = mysql40_req;
4631 mysql_rsname = mysql40_rsname;
4632 }
4633 else {
4634 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4635 file, line, args[cur_arg], args[cur_arg+2]);
4636 goto error;
4637 }
4638
4639 hdr[0] = (unsigned char)(packetlen & 0xff);
4640 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4641 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4642 hdr[3] = 1;
4643
4644 var = create_tcpcheck_var(ist("check.header"));
4645 if (var == NULL) {
4646 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4647 goto error;
4648 }
4649 var->data.type = SMP_T_STR;
4650 var->data.u.str.area = hdr;
4651 var->data.u.str.data = 4;
4652 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004653 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004654 hdr = NULL;
4655 var = NULL;
4656
4657 var = create_tcpcheck_var(ist("check.username"));
4658 if (var == NULL) {
4659 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4660 goto error;
4661 }
4662 var->data.type = SMP_T_STR;
4663 var->data.u.str.area = user;
4664 var->data.u.str.data = strlen(user);
4665 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004666 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004667 user = NULL;
4668 var = NULL;
4669 }
4670
4671 rs = find_tcpcheck_ruleset(mysql_rsname);
4672 if (rs)
4673 goto ruleset_found;
4674
4675 rs = create_tcpcheck_ruleset(mysql_rsname);
4676 if (rs == NULL) {
4677 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4678 goto error;
4679 }
4680
4681 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4682 1, curpx, &rs->rules, file, line, &errmsg);
4683 if (!chk) {
4684 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4685 goto error;
4686 }
4687 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004688 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004689
4690 if (mysql_req) {
4691 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4692 1, curpx, &rs->rules, file, line, &errmsg);
4693 if (!chk) {
4694 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4695 goto error;
4696 }
4697 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004698 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004699 }
4700
4701 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4702 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4703 if (!chk) {
4704 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4705 goto error;
4706 }
4707 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4708 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004709 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004710
4711 if (mysql_req) {
4712 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4713 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4714 if (!chk) {
4715 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4716 goto error;
4717 }
4718 chk->expect.custom = tcpcheck_mysql_expect_ok;
4719 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004720 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004721 }
4722
4723 ruleset_found:
4724 rules->list = &rs->rules;
4725 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4726 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4727
4728 out:
4729 free(errmsg);
4730 return err_code;
4731
4732 error:
4733 free(hdr);
4734 free(user);
4735 free(var);
4736 free_tcpcheck_vars(&rules->preset_vars);
4737 free_tcpcheck_ruleset(rs);
4738 err_code |= ERR_ALERT | ERR_FATAL;
4739 goto out;
4740}
4741
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004742int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004743 const char *file, int line)
4744{
4745 static char *ldap_req = "300C020101600702010304008000";
4746
4747 struct tcpcheck_ruleset *rs = NULL;
4748 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4749 struct tcpcheck_rule *chk;
4750 char *errmsg = NULL;
4751 int err_code = 0;
4752
4753 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4754 err_code |= ERR_WARN;
4755
4756 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4757 goto out;
4758
4759 curpx->options2 &= ~PR_O2_CHK_ANY;
4760 curpx->options2 |= PR_O2_TCPCHK_CHK;
4761
4762 free_tcpcheck_vars(&rules->preset_vars);
4763 rules->list = NULL;
4764 rules->flags = 0;
4765
4766 rs = find_tcpcheck_ruleset("*ldap-check");
4767 if (rs)
4768 goto ruleset_found;
4769
4770 rs = create_tcpcheck_ruleset("*ldap-check");
4771 if (rs == NULL) {
4772 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4773 goto error;
4774 }
4775
4776 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4777 1, curpx, &rs->rules, file, line, &errmsg);
4778 if (!chk) {
4779 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4780 goto error;
4781 }
4782 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004783 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004784
4785 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4786 "min-recv", "14",
4787 "on-error", "Not LDAPv3 protocol",
4788 ""},
4789 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4790 if (!chk) {
4791 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4792 goto error;
4793 }
4794 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004795 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004796
4797 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4798 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4799 if (!chk) {
4800 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4801 goto error;
4802 }
4803 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4804 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004805 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004806
4807 ruleset_found:
4808 rules->list = &rs->rules;
4809 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4810 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4811
4812 out:
4813 free(errmsg);
4814 return err_code;
4815
4816 error:
4817 free_tcpcheck_ruleset(rs);
4818 err_code |= ERR_ALERT | ERR_FATAL;
4819 goto out;
4820}
4821
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004822int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004823 const char *file, int line)
4824{
4825 struct tcpcheck_ruleset *rs = NULL;
4826 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4827 struct tcpcheck_rule *chk;
4828 char *spop_req = NULL;
4829 char *errmsg = NULL;
4830 int spop_len = 0, err_code = 0;
4831
4832 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4833 err_code |= ERR_WARN;
4834
4835 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4836 goto out;
4837
4838 curpx->options2 &= ~PR_O2_CHK_ANY;
4839 curpx->options2 |= PR_O2_TCPCHK_CHK;
4840
4841 free_tcpcheck_vars(&rules->preset_vars);
4842 rules->list = NULL;
4843 rules->flags = 0;
4844
4845
4846 rs = find_tcpcheck_ruleset("*spop-check");
4847 if (rs)
4848 goto ruleset_found;
4849
4850 rs = create_tcpcheck_ruleset("*spop-check");
4851 if (rs == NULL) {
4852 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4853 goto error;
4854 }
4855
4856 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4857 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4858 goto error;
4859 }
4860 chunk_reset(&trash);
4861 dump_binary(&trash, spop_req, spop_len);
4862 trash.area[trash.data] = '\0';
4863
4864 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4865 1, curpx, &rs->rules, file, line, &errmsg);
4866 if (!chk) {
4867 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4868 goto error;
4869 }
4870 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004871 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004872
4873 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4874 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4875 if (!chk) {
4876 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4877 goto error;
4878 }
4879 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4880 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004881 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004882
4883 ruleset_found:
4884 rules->list = &rs->rules;
4885 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4886 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4887
4888 out:
4889 free(spop_req);
4890 free(errmsg);
4891 return err_code;
4892
4893 error:
4894 free_tcpcheck_ruleset(rs);
4895 err_code |= ERR_ALERT | ERR_FATAL;
4896 goto out;
4897}
4898
4899
4900static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4901{
4902 struct tcpcheck_rule *chk = NULL;
4903 struct tcpcheck_http_hdr *hdr = NULL;
4904 char *meth = NULL, *uri = NULL, *vsn = NULL;
4905 char *hdrs, *body;
4906
4907 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4908 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4909 if (hdrs == body)
4910 hdrs = NULL;
4911 if (hdrs) {
4912 *hdrs = '\0';
4913 hdrs +=2;
4914 }
4915 if (body) {
4916 *body = '\0';
4917 body += 4;
4918 }
4919 if (hdrs || body) {
4920 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4921 " Please, consider to use 'http-check send' directive instead.");
4922 }
4923
4924 chk = calloc(1, sizeof(*chk));
4925 if (!chk) {
4926 memprintf(errmsg, "out of memory");
4927 goto error;
4928 }
4929 chk->action = TCPCHK_ACT_SEND;
4930 chk->send.type = TCPCHK_SEND_HTTP;
4931 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4932 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4933 LIST_INIT(&chk->send.http.hdrs);
4934
4935 /* Copy the method, uri and version */
4936 if (*args[cur_arg]) {
4937 if (!*args[cur_arg+1])
4938 uri = args[cur_arg];
4939 else
4940 meth = args[cur_arg];
4941 }
4942 if (*args[cur_arg+1])
4943 uri = args[cur_arg+1];
4944 if (*args[cur_arg+2])
4945 vsn = args[cur_arg+2];
4946
4947 if (meth) {
4948 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4949 chk->send.http.meth.str.area = strdup(meth);
4950 chk->send.http.meth.str.data = strlen(meth);
4951 if (!chk->send.http.meth.str.area) {
4952 memprintf(errmsg, "out of memory");
4953 goto error;
4954 }
4955 }
4956 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004957 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004958 if (!isttest(chk->send.http.uri)) {
4959 memprintf(errmsg, "out of memory");
4960 goto error;
4961 }
4962 }
4963 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004964 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004965 if (!isttest(chk->send.http.vsn)) {
4966 memprintf(errmsg, "out of memory");
4967 goto error;
4968 }
4969 }
4970
4971 /* Copy the header */
4972 if (hdrs) {
4973 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4974 struct h1m h1m;
4975 int i, ret;
4976
4977 /* Build and parse the request */
4978 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4979
4980 h1m.flags = H1_MF_HDRS_ONLY;
4981 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4982 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4983 &h1m, NULL);
4984 if (ret <= 0) {
4985 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4986 goto error;
4987 }
4988
4989 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4990 hdr = calloc(1, sizeof(*hdr));
4991 if (!hdr) {
4992 memprintf(errmsg, "out of memory");
4993 goto error;
4994 }
4995 LIST_INIT(&hdr->value);
4996 hdr->name = istdup(tmp_hdrs[i].n);
Tim Duesterhus77508502022-03-15 13:11:06 +01004997 if (!isttest(hdr->name)) {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004998 memprintf(errmsg, "out of memory");
4999 goto error;
5000 }
5001
5002 ist0(tmp_hdrs[i].v);
5003 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5004 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005005 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005006 }
5007 }
5008
5009 /* Copy the body */
5010 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005011 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005012 if (!isttest(chk->send.http.body)) {
5013 memprintf(errmsg, "out of memory");
5014 goto error;
5015 }
5016 }
5017
5018 return chk;
5019
5020 error:
5021 free_tcpcheck_http_hdr(hdr);
5022 free_tcpcheck(chk, 0);
5023 return NULL;
5024}
5025
5026/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005027int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005028 const char *file, int line)
5029{
5030 struct tcpcheck_ruleset *rs = NULL;
5031 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5032 struct tcpcheck_rule *chk;
5033 char *errmsg = NULL;
5034 int err_code = 0;
5035
5036 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5037 err_code |= ERR_WARN;
5038
5039 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5040 goto out;
5041
5042 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5043 if (!chk) {
5044 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5045 goto error;
5046 }
5047 if (errmsg) {
5048 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5049 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005050 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005051 }
5052
5053 no_request:
5054 curpx->options2 &= ~PR_O2_CHK_ANY;
5055 curpx->options2 |= PR_O2_TCPCHK_CHK;
5056
5057 free_tcpcheck_vars(&rules->preset_vars);
5058 rules->list = NULL;
5059 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5060
5061 /* Deduce the ruleset name from the proxy info */
5062 chunk_printf(&trash, "*http-check-%s_%s-%d",
5063 ((curpx == defpx) ? "defaults" : curpx->id),
5064 curpx->conf.file, curpx->conf.line);
5065
5066 rs = find_tcpcheck_ruleset(b_orig(&trash));
5067 if (rs == NULL) {
5068 rs = create_tcpcheck_ruleset(b_orig(&trash));
5069 if (rs == NULL) {
5070 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5071 goto error;
5072 }
5073 }
5074
5075 rules->list = &rs->rules;
5076 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5077 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5078 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5079 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5080 rules->list = NULL;
5081 goto error;
5082 }
5083
5084 out:
5085 free(errmsg);
5086 return err_code;
5087
5088 error:
5089 free_tcpcheck_ruleset(rs);
5090 free_tcpcheck(chk, 0);
5091 err_code |= ERR_ALERT | ERR_FATAL;
5092 goto out;
5093}
5094
5095/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005096int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005097 const char *file, int line)
5098{
5099 struct tcpcheck_ruleset *rs = NULL;
5100 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5101 int err_code = 0;
5102
5103 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5104 err_code |= ERR_WARN;
5105
5106 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5107 goto out;
5108
5109 curpx->options2 &= ~PR_O2_CHK_ANY;
5110 curpx->options2 |= PR_O2_TCPCHK_CHK;
5111
5112 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5113 /* If a tcp-check rulesset is already set, do nothing */
5114 if (rules->list)
5115 goto out;
5116
5117 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5118 * get it.
5119 */
5120 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5121 goto curpx_ruleset;
5122
5123 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5124 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5125 rs = find_tcpcheck_ruleset(b_orig(&trash));
5126 if (rs)
5127 goto ruleset_found;
5128 }
5129
5130 curpx_ruleset:
5131 /* Deduce the ruleset name from the proxy info */
5132 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5133 ((curpx == defpx) ? "defaults" : curpx->id),
5134 curpx->conf.file, curpx->conf.line);
5135
5136 rs = find_tcpcheck_ruleset(b_orig(&trash));
5137 if (rs == NULL) {
5138 rs = create_tcpcheck_ruleset(b_orig(&trash));
5139 if (rs == NULL) {
5140 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5141 goto error;
5142 }
5143 }
5144
5145 ruleset_found:
5146 free_tcpcheck_vars(&rules->preset_vars);
5147 rules->list = &rs->rules;
5148 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5149 rules->flags |= TCPCHK_RULES_TCP_CHK;
5150
5151 out:
5152 return err_code;
5153
5154 error:
5155 err_code |= ERR_ALERT | ERR_FATAL;
5156 goto out;
5157}
5158
Willy Tarreau51cd5952020-06-05 12:25:38 +02005159static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005160 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005161 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5162 { 0, NULL, NULL },
5163}};
5164
5165REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5166REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5167REGISTER_POST_DEINIT(deinit_tcpchecks);
5168INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);