blob: 56d2b31dbb22e68cbc76b5189968a41435ca819d [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>
27#include <fcntl.h>
28#include <signal.h>
29#include <stdarg.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <time.h>
34#include <unistd.h>
35
36#include <haproxy/action.h>
37#include <haproxy/api.h>
38#include <haproxy/cfgparse.h>
39#include <haproxy/check.h>
40#include <haproxy/chunk.h>
41#include <haproxy/connection.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 Faulet81011212021-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 Tarreaub2551052020-06-09 09:07:15 +020060#include <haproxy/time.h>
61#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)) {
432 chunk_strncat(msg, istptr(info), istlen(info));
433 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))
520 chunk_strncat(msg, istptr(info), istlen(info));
521 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 Faulet81011212021-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 Faulet81011212021-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 Faulet81011212021-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 Faulet81011212021-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 Faulet81011212021-09-16 16:01:09 +0200707 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200708
Christopher Faulet81011212021-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 Faulet81011212021-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 Faulet81011212021-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 Faulet81011212021-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 Faulet81011212021-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 Denoyellecaaafd02021-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 Fauletb1bb0692020-11-25 16:47:30 +01001059 struct conn_stream *cs = check->cs;
1060 struct connection *conn = cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001061 struct protocol *proto;
1062 struct xprt_ops *xprt;
1063 struct tcpcheck_rule *next;
1064 int status, port;
1065
Christopher Faulet147b8c92021-04-10 09:00:38 +02001066 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1067
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001068 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1069
1070 /* current connection already created, check if it is established or not */
1071 if (conn) {
1072 if (conn->flags & CO_FL_WAIT_XPRT) {
1073 /* We are still waiting for the connection establishment */
1074 if (next && next->action == TCPCHK_ACT_SEND) {
1075 if (!(check->wait_list.events & SUB_RETRY_SEND))
1076 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1077 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001078 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001079 }
1080 else
1081 ret = tcpcheck_eval_recv(check, rule);
1082 }
1083 goto out;
1084 }
1085
1086 /* Note: here check->cs = cs = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001087
Christopher Fauletb381a502020-11-25 13:47:00 +01001088 /* Always release input and output buffer when a new connect is evaluated */
1089 check_release_buf(check, &check->bi);
1090 check_release_buf(check, &check->bo);
1091
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001092 /* No connection, prepare a new one */
Christopher Faulet236c93b2020-07-02 09:19:54 +02001093 cs = cs_new(NULL, (s ? &s->obj_type : &proxy->obj_type));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001094 if (!cs) {
1095 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1096 tcpcheck_get_step_id(check, rule));
1097 if (rule->comment)
1098 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1099 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1100 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001101 TRACE_ERROR("conn-stream allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001102 goto out;
1103 }
1104
Willy Tarreau51cd5952020-06-05 12:25:38 +02001105 tasklet_set_tid(check->wait_list.tasklet, tid);
1106
1107 check->cs = cs;
1108 conn = cs->conn;
1109 conn_set_owner(conn, check->sess, NULL);
1110
1111 /* Maybe there were an older connection we were waiting on */
1112 check->wait_list.events = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001113
1114 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001115 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001116 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001117 status = SF_ERR_RESOURCE;
1118 goto fail_check;
1119 }
1120
1121 /* connect to the connect rule addr if specified, otherwise the check
1122 * addr if specified on the server. otherwise, use the server addr (it
1123 * MUST exist at this step).
1124 */
1125 *conn->dst = (is_addr(&connect->addr)
1126 ? connect->addr
1127 : (is_addr(&check->addr) ? check->addr : s->addr));
1128 proto = protocol_by_family(conn->dst->ss_family);
1129
1130 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001131 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001132 port = connect->port;
1133 if (!port && connect->port_expr) {
1134 struct sample *smp;
1135
1136 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1137 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1138 connect->port_expr, SMP_T_SINT);
1139 if (smp)
1140 port = smp->data.u.sint;
1141 }
1142 if (!port && is_inet_addr(&connect->addr))
1143 port = get_host_port(&connect->addr);
1144 if (!port && check->port)
1145 port = check->port;
1146 if (!port && is_inet_addr(&check->addr))
1147 port = get_host_port(&check->addr);
1148 if (!port) {
1149 /* The server MUST exist here */
1150 port = s->svc_port;
1151 }
1152 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001153 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001154
1155 xprt = ((connect->options & TCPCHK_OPT_SSL)
1156 ? xprt_get(XPRT_SSL)
1157 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1158
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001159 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001160 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001161 status = SF_ERR_RESOURCE;
1162 goto fail_check;
1163 }
1164
Willy Tarreau51cd5952020-06-05 12:25:38 +02001165 cs_attach(cs, check, &check_conn_cb);
1166
Christopher Fauletf7177272020-10-02 13:41:55 +02001167 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1168 conn->send_proxy_ofs = 1;
1169 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001170 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001171 }
1172 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1173 conn->send_proxy_ofs = 1;
1174 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001175 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001176 }
1177
1178 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1179 conn->send_proxy_ofs = 1;
1180 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001181 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001182 }
1183 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1184 conn->send_proxy_ofs = 1;
1185 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001186 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001187 }
1188
Willy Tarreau51cd5952020-06-05 12:25:38 +02001189 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001190 if (proto && proto->connect) {
1191 int flags = 0;
1192
Christopher Faulet004ffe92022-08-30 10:31:15 +02001193 if (!next)
1194 flags |= CONNECT_DELACK_ALWAYS;
Christopher Faulet8af4ab82022-08-24 11:38:03 +02001195 if (connect->options & TCPCHK_OPT_HAS_DATA)
Christopher Faulet004ffe92022-08-30 10:31:15 +02001196 flags |= (CONNECT_HAS_DATA|CONNECT_DELACK_ALWAYS);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001197 status = proto->connect(conn, flags);
1198 }
1199
1200 if (status != SF_ERR_NONE)
1201 goto fail_check;
1202
Christopher Faulet21ddc742020-07-01 15:26:14 +02001203 conn_set_private(conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001204 conn->ctx = cs;
1205
Willy Tarreau51cd5952020-06-05 12:25:38 +02001206#ifdef USE_OPENSSL
1207 if (connect->sni)
1208 ssl_sock_set_servername(conn, connect->sni);
1209 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1210 ssl_sock_set_servername(conn, s->check.sni);
1211
1212 if (connect->alpn)
1213 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1214 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1215 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1216#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001217
1218 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1219 /* Some servers don't like reset on close */
Willy Tarreaub41a6e92021-04-06 17:49:19 +02001220 HA_ATOMIC_AND(&fdtab[cs->conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001221 }
1222
1223 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1224 if (xprt_add_hs(conn) < 0)
1225 status = SF_ERR_RESOURCE;
1226 }
1227
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001228 if (conn_xprt_start(conn) < 0) {
1229 status = SF_ERR_RESOURCE;
1230 goto fail_check;
1231 }
1232
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001233 /* The mux may be initialized now if there isn't server attached to the
1234 * check (email alerts) or if there is a mux proto specified or if there
1235 * is no alpn.
1236 */
1237 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1238 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1239 const struct mux_ops *mux_ops;
1240
Christopher Faulet147b8c92021-04-10 09:00:38 +02001241 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001242 if (connect->mux_proto)
1243 mux_ops = connect->mux_proto->mux;
1244 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1245 mux_ops = check->mux_proto->mux;
1246 else {
1247 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1248 ? PROTO_MODE_HTTP
1249 : PROTO_MODE_TCP);
1250
1251 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1252 }
1253 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001254 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001255 status = SF_ERR_INTERNAL;
1256 goto fail_check;
1257 }
1258 }
1259
Willy Tarreau51cd5952020-06-05 12:25:38 +02001260 fail_check:
1261 /* It can return one of :
1262 * - SF_ERR_NONE if everything's OK
1263 * - SF_ERR_SRVTO if there are no more servers
1264 * - SF_ERR_SRVCL if the connection was refused by the server
1265 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1266 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1267 * - SF_ERR_INTERNAL for any other purely internal errors
1268 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1269 * Note that we try to prevent the network stack from sending the ACK during the
1270 * connect() when a pure TCP check is used (without PROXY protocol).
1271 */
1272 switch (status) {
1273 case SF_ERR_NONE:
1274 /* we allow up to min(inter, timeout.connect) for a connection
1275 * to establish but only when timeout.check is set as it may be
1276 * to short for a full check otherwise
1277 */
1278 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1279
1280 if (proxy->timeout.check && proxy->timeout.connect) {
1281 int t_con = tick_add(now_ms, proxy->timeout.connect);
1282 t->expire = tick_first(t->expire, t_con);
1283 }
1284 break;
1285 case SF_ERR_SRVTO: /* ETIMEDOUT */
1286 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1287 case SF_ERR_PRXCOND:
1288 case SF_ERR_RESOURCE:
1289 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001290 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 +02001291 chk_report_conn_err(check, errno, 0);
1292 ret = TCPCHK_EVAL_STOP;
1293 goto out;
1294 }
1295
1296 /* don't do anything until the connection is established */
1297 if (conn->flags & CO_FL_WAIT_XPRT) {
1298 if (conn->mux) {
1299 if (next && next->action == TCPCHK_ACT_SEND)
1300 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1301 else
1302 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
1303 }
1304 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001305 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001306 goto out;
1307 }
1308
1309 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001310 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001311 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001312 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001313 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001314
1315 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1316 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1317
Christopher Faulet147b8c92021-04-10 09:00:38 +02001318 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001319 return ret;
1320}
1321
1322/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1323 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1324 * TCPCHK_EVAL_STOP if an error occurred.
1325 */
1326enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1327{
1328 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1329 struct tcpcheck_send *send = &rule->send;
1330 struct conn_stream *cs = check->cs;
1331 struct connection *conn = cs_conn(cs);
1332 struct buffer *tmp = NULL;
1333 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001334 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001335
Christopher Faulet147b8c92021-04-10 09:00:38 +02001336 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1337
Christopher Fauletb381a502020-11-25 13:47:00 +01001338 if (check->state & CHK_ST_OUT_ALLOC) {
1339 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001340 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 +01001341 goto out;
1342 }
1343
1344 if (!check_get_buf(check, &check->bo)) {
1345 check->state |= CHK_ST_OUT_ALLOC;
1346 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001347 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 +01001348 goto out;
1349 }
1350
Christopher Faulet39066c22020-11-25 13:34:51 +01001351 /* Data already pending in the output buffer, send them now */
Christopher Faulet18280ca2021-08-11 15:46:29 +02001352 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 +02001353 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 +01001354 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001355 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001356
Christopher Fauletb381a502020-11-25 13:47:00 +01001357 /* Always release input buffer when a new send is evaluated */
1358 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001359
1360 switch (send->type) {
1361 case TCPCHK_SEND_STRING:
1362 case TCPCHK_SEND_BINARY:
1363 if (istlen(send->data) >= b_size(&check->bo)) {
1364 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1365 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1366 tcpcheck_get_step_id(check, rule));
1367 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1368 ret = TCPCHK_EVAL_STOP;
1369 goto out;
1370 }
1371 b_putist(&check->bo, send->data);
1372 break;
1373 case TCPCHK_SEND_STRING_LF:
1374 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1375 if (!b_data(&check->bo))
1376 goto out;
1377 break;
1378 case TCPCHK_SEND_BINARY_LF: {
1379 int len = b_size(&check->bo);
1380
1381 tmp = alloc_trash_chunk();
1382 if (!tmp)
1383 goto error_lf;
1384 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1385 if (!b_data(tmp))
1386 goto out;
1387 tmp->area[tmp->data] = '\0';
1388 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1389 goto error_lf;
1390 check->bo.data = len;
1391 break;
1392 }
1393 case TCPCHK_SEND_HTTP: {
1394 struct htx_sl *sl;
1395 struct ist meth, uri, vsn, clen, body;
1396 unsigned int slflags = 0;
1397
1398 tmp = alloc_trash_chunk();
1399 if (!tmp)
1400 goto error_htx;
1401
1402 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1403 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1404 : http_known_methods[send->http.meth.meth]);
1405 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1406 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1407 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1408 }
1409 else
1410 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1411 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1412
1413 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1414 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1415 slflags |= HTX_SL_F_VER_11;
1416 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1417 if (!isttest(send->http.body))
1418 slflags |= HTX_SL_F_BODYLESS;
1419
1420 htx = htx_from_buf(&check->bo);
1421 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1422 if (!sl)
1423 goto error_htx;
1424 sl->info.req.meth = send->http.meth.meth;
1425 if (!http_update_host(htx, sl, uri))
1426 goto error_htx;
1427
1428 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1429 struct tcpcheck_http_hdr *hdr;
1430 struct ist hdr_value;
1431
1432 list_for_each_entry(hdr, &send->http.hdrs, list) {
1433 chunk_reset(tmp);
1434 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1435 if (!b_data(tmp))
1436 continue;
1437 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1438 if (!htx_add_header(htx, hdr->name, hdr_value))
1439 goto error_htx;
1440 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1441 if (!http_update_authority(htx, sl, hdr_value))
1442 goto error_htx;
1443 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001444 if (isteqi(hdr->name, ist("connection")))
1445 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001446 }
1447
1448 }
1449 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1450 chunk_reset(tmp);
1451 httpchk_build_status_header(check->server, tmp);
1452 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1453 goto error_htx;
1454 }
1455
1456
1457 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1458 chunk_reset(tmp);
1459 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1460 body = ist2(b_orig(tmp), b_data(tmp));
1461 }
1462 else
1463 body = send->http.body;
1464 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1465
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001466 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001467 !htx_add_header(htx, ist("Content-length"), clen))
1468 goto error_htx;
1469
1470
1471 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001472 (istlen(body) && !htx_add_data_atonce(htx, body)))
1473 goto error_htx;
1474
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001475 /* no more data are expected */
1476 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001477 htx_to_buf(htx, &check->bo);
1478 break;
1479 }
1480 case TCPCHK_SEND_UNDEF:
1481 /* Should never happen. */
1482 ret = TCPCHK_EVAL_STOP;
1483 goto out;
1484 };
1485
Christopher Faulet39066c22020-11-25 13:34:51 +01001486 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001487 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001488 if (conn->mux->snd_buf(cs, &check->bo,
1489 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1490 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1491 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001492 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 +02001493 goto out;
1494 }
1495 }
Christopher Faulet18280ca2021-08-11 15:46:29 +02001496 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001497 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1498 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001499 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001500 goto out;
1501 }
1502
1503 out:
1504 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001505 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1506 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001507
1508 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001509 return ret;
1510
1511 error_htx:
1512 if (htx) {
1513 htx_reset(htx);
1514 htx_to_buf(htx, &check->bo);
1515 }
1516 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1517 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001518 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 +02001519 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1520 ret = TCPCHK_EVAL_STOP;
1521 goto out;
1522
1523 error_lf:
1524 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1525 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001526 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 +02001527 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1528 ret = TCPCHK_EVAL_STOP;
1529 goto out;
1530
1531}
1532
1533/* Try to receive data before evaluating a tcp-check expect rule. Returns
1534 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1535 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1536 * TCPCHK_EVAL_STOP if an error occurred.
1537 */
1538enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1539{
1540 struct conn_stream *cs = check->cs;
1541 struct connection *conn = cs_conn(cs);
1542 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1543 size_t max, read, cur_read = 0;
1544 int is_empty;
1545 int read_poll = MAX_READ_POLL_LOOPS;
1546
Christopher Faulet147b8c92021-04-10 09:00:38 +02001547 TRACE_ENTER(CHK_EV_RX_DATA, check);
1548
1549 if (check->wait_list.events & SUB_RETRY_RECV) {
1550 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001551 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001552 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001553
1554 if (cs->flags & CS_FL_EOS)
1555 goto end_recv;
1556
Christopher Faulet147b8c92021-04-10 09:00:38 +02001557 if (check->state & CHK_ST_IN_ALLOC) {
1558 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001559 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001560 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001561
1562 if (!check_get_buf(check, &check->bi)) {
1563 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001564 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001565 goto wait_more_data;
1566 }
1567
Willy Tarreau51cd5952020-06-05 12:25:38 +02001568 /* errors on the connection and the conn-stream were already checked */
1569
1570 /* prepare to detect if the mux needs more room */
1571 cs->flags &= ~CS_FL_WANT_ROOM;
1572
1573 while ((cs->flags & CS_FL_RCV_MORE) ||
1574 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1575 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1576 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1577 cur_read += read;
1578 if (!read ||
1579 (cs->flags & CS_FL_WANT_ROOM) ||
1580 (--read_poll <= 0) ||
1581 (read < max && read >= global.tune.recv_enough))
1582 break;
1583 }
1584
1585 end_recv:
1586 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1587 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1588 /* Report network errors only if we got no other data. Otherwise
1589 * we'll let the upper layers decide whether the response is OK
1590 * or not. It is very common that an RST sent by the server is
1591 * reported as an error just after the last data chunk.
1592 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001593 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001594 goto stop;
1595 }
1596 if (!cur_read) {
Christopher Fauletbd017472021-10-20 13:53:38 +02001597 if (cs->flags & CS_FL_EOI) {
1598 /* If EOI is set, it means there is a response or an error */
1599 goto out;
1600 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001601 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1602 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001603 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001604 goto wait_more_data;
1605 }
1606 if (is_empty) {
1607 int status;
1608
1609 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1610 tcpcheck_get_step_id(check, rule));
1611 if (rule->comment)
1612 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1613
Christopher Faulet147b8c92021-04-10 09:00:38 +02001614 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001615 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1616 set_server_check_status(check, status, trash.area);
1617 goto stop;
1618 }
1619 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001620 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001621
1622 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001623 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1624 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001625
1626 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001627 return ret;
1628
1629 stop:
1630 ret = TCPCHK_EVAL_STOP;
1631 goto out;
1632
1633 wait_more_data:
1634 ret = TCPCHK_EVAL_WAIT;
1635 goto out;
1636}
1637
1638/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1639 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1640 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1641 * error occurred.
1642 */
1643enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1644{
1645 struct htx *htx = htxbuf(&check->bi);
1646 struct htx_sl *sl;
1647 struct htx_blk *blk;
1648 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1649 struct tcpcheck_expect *expect = &rule->expect;
1650 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1651 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1652 struct ist desc = IST_NULL;
1653 int i, match, inverse;
1654
Christopher Faulet147b8c92021-04-10 09:00:38 +02001655 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1656
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001657 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001658
1659 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001660 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001661 status = HCHK_STATUS_L7RSP;
1662 goto error;
1663 }
1664
1665 if (htx_is_empty(htx)) {
1666 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001667 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001668 status = HCHK_STATUS_L7RSP;
1669 goto error;
1670 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001671 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001672 goto wait_more_data;
1673 }
1674
1675 sl = http_get_stline(htx);
1676 check->code = sl->info.res.status;
1677
1678 if (check->server &&
1679 (check->server->proxy->options & PR_O_DISABLE404) &&
1680 (check->server->next_state != SRV_ST_STOPPED) &&
1681 (check->code == 404)) {
1682 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001683 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001684 goto out;
1685 }
1686
1687 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1688 /* Make GCC happy ; initialize match to a failure state. */
1689 match = inverse;
1690 status = expect->err_status;
1691
1692 switch (expect->type) {
1693 case TCPCHK_EXPECT_HTTP_STATUS:
1694 match = 0;
1695 for (i = 0; i < expect->codes.num; i++) {
1696 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1697 sl->info.res.status <= expect->codes.codes[i][1]) {
1698 match = 1;
1699 break;
1700 }
1701 }
1702
1703 /* Set status and description in case of error */
1704 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1705 if (LIST_ISEMPTY(&expect->onerror_fmt))
1706 desc = htx_sl_res_reason(sl);
1707 break;
1708 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1709 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1710
1711 /* Set status and description in case of error */
1712 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1713 if (LIST_ISEMPTY(&expect->onerror_fmt))
1714 desc = htx_sl_res_reason(sl);
1715 break;
1716
1717 case TCPCHK_EXPECT_HTTP_HEADER: {
1718 struct http_hdr_ctx ctx;
1719 struct ist npat, vpat, value;
1720 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1721
1722 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1723 nbuf = alloc_trash_chunk();
1724 if (!nbuf) {
1725 status = HCHK_STATUS_L7RSP;
1726 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001727 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001728 goto error;
1729 }
1730 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1731 if (!b_data(nbuf)) {
1732 status = HCHK_STATUS_L7RSP;
1733 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001734 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001735 goto error;
1736 }
1737 npat = ist2(b_orig(nbuf), b_data(nbuf));
1738 }
1739 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1740 npat = expect->hdr.name;
1741
1742 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1743 vbuf = alloc_trash_chunk();
1744 if (!vbuf) {
1745 status = HCHK_STATUS_L7RSP;
1746 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001747 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001748 goto error;
1749 }
1750 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1751 if (!b_data(vbuf)) {
1752 status = HCHK_STATUS_L7RSP;
1753 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001754 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001755 goto error;
1756 }
1757 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1758 }
1759 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1760 vpat = expect->hdr.value;
1761
1762 match = 0;
1763 ctx.blk = NULL;
1764 while (1) {
1765 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1766 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1767 if (!http_find_str_header(htx, npat, &ctx, full))
1768 goto end_of_match;
1769 break;
1770 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1771 if (!http_find_pfx_header(htx, npat, &ctx, full))
1772 goto end_of_match;
1773 break;
1774 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1775 if (!http_find_sfx_header(htx, npat, &ctx, full))
1776 goto end_of_match;
1777 break;
1778 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1779 if (!http_find_sub_header(htx, npat, &ctx, full))
1780 goto end_of_match;
1781 break;
1782 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1783 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1784 goto end_of_match;
1785 break;
1786 default:
1787 /* should never happen */
1788 goto end_of_match;
1789 }
1790
1791 /* A header has matched the name pattern, let's test its
1792 * value now (always defined from there). If there is no
1793 * value pattern, it is a good match.
1794 */
1795
1796 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1797 match = 1;
1798 goto end_of_match;
1799 }
1800
1801 value = ctx.value;
1802 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1803 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1804 if (isteq(value, vpat)) {
1805 match = 1;
1806 goto end_of_match;
1807 }
1808 break;
1809 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1810 if (istlen(value) < istlen(vpat))
1811 break;
1812 value = ist2(istptr(value), istlen(vpat));
1813 if (isteq(value, vpat)) {
1814 match = 1;
1815 goto end_of_match;
1816 }
1817 break;
1818 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1819 if (istlen(value) < istlen(vpat))
1820 break;
1821 value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
1822 if (isteq(value, vpat)) {
1823 match = 1;
1824 goto end_of_match;
1825 }
1826 break;
1827 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1828 if (isttest(istist(value, vpat))) {
1829 match = 1;
1830 goto end_of_match;
1831 }
1832 break;
1833 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1834 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1835 match = 1;
1836 goto end_of_match;
1837 }
1838 break;
1839 }
1840 }
1841
1842 end_of_match:
1843 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1844 if (LIST_ISEMPTY(&expect->onerror_fmt))
1845 desc = htx_sl_res_reason(sl);
1846 break;
1847 }
1848
1849 case TCPCHK_EXPECT_HTTP_BODY:
1850 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1851 case TCPCHK_EXPECT_HTTP_BODY_LF:
1852 match = 0;
1853 chunk_reset(&trash);
1854 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1855 enum htx_blk_type type = htx_get_blk_type(blk);
1856
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001857 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001858 break;
1859 if (type == HTX_BLK_DATA) {
1860 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1861 break;
1862 }
1863 }
1864
1865 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001866 if (!last_read) {
1867 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001868 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001869 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001870 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1871 if (LIST_ISEMPTY(&expect->onerror_fmt))
1872 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001873 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001874 goto error;
1875 }
1876
1877 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1878 tmp = alloc_trash_chunk();
1879 if (!tmp) {
1880 status = HCHK_STATUS_L7RSP;
1881 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001882 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001883 goto error;
1884 }
1885 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1886 if (!b_data(tmp)) {
1887 status = HCHK_STATUS_L7RSP;
1888 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001889 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001890 goto error;
1891 }
1892 }
1893
1894 if (!last_read &&
1895 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1896 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1897 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1898 ret = TCPCHK_EVAL_WAIT;
1899 goto out;
1900 }
1901
1902 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1903 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1904 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1905 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1906 else
1907 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1908
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001909 /* Wait for more data on mismatch only if no minimum is defined (-1),
1910 * otherwise the absence of match is already conclusive.
1911 */
1912 if (!match && !last_read && (expect->min_recv == -1)) {
1913 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001914 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001915 goto out;
1916 }
1917
Willy Tarreau51cd5952020-06-05 12:25:38 +02001918 /* Set status and description in case of error */
1919 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1920 if (LIST_ISEMPTY(&expect->onerror_fmt))
1921 desc = (inverse
1922 ? ist("HTTP check matched unwanted content")
1923 : ist("HTTP content check did not match"));
1924 break;
1925
1926
1927 default:
1928 /* should never happen */
1929 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1930 goto error;
1931 }
1932
Christopher Faulet147b8c92021-04-10 09:00:38 +02001933 if (!(match ^ inverse)) {
1934 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001935 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001936 }
1937
1938 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001939
1940 out:
1941 free_trash_chunk(tmp);
1942 free_trash_chunk(nbuf);
1943 free_trash_chunk(vbuf);
1944 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001945 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001946 return ret;
1947
1948 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001949 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001950 ret = TCPCHK_EVAL_STOP;
1951 msg = alloc_trash_chunk();
1952 if (msg)
1953 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1954 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1955 goto out;
1956
1957 wait_more_data:
1958 ret = TCPCHK_EVAL_WAIT;
1959 goto out;
1960}
1961
1962/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1963 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1964 * if an error occurred.
1965 */
1966enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1967{
1968 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1969 struct tcpcheck_expect *expect = &rule->expect;
1970 struct buffer *msg = NULL, *tmp = NULL;
1971 struct ist desc = IST_NULL;
1972 enum healthcheck_status status;
1973 int match, inverse;
1974
Christopher Faulet147b8c92021-04-10 09:00:38 +02001975 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1976
Willy Tarreau51cd5952020-06-05 12:25:38 +02001977 last_read |= b_full(&check->bi);
1978
1979 /* The current expect might need more data than the previous one, check again
1980 * that the minimum amount data required to match is respected.
1981 */
1982 if (!last_read) {
1983 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1984 (b_data(&check->bi) < istlen(expect->data))) {
1985 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001986 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001987 goto out;
1988 }
1989 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1990 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001991 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001992 goto out;
1993 }
1994 }
1995
1996 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1997 /* Make GCC happy ; initialize match to a failure state. */
1998 match = inverse;
1999 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
2000
2001 switch (expect->type) {
2002 case TCPCHK_EXPECT_STRING:
2003 case TCPCHK_EXPECT_BINARY:
2004 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2005 break;
2006 case TCPCHK_EXPECT_STRING_REGEX:
2007 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2008 break;
2009
2010 case TCPCHK_EXPECT_BINARY_REGEX:
2011 chunk_reset(&trash);
2012 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2013 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2014 break;
2015
2016 case TCPCHK_EXPECT_STRING_LF:
2017 case TCPCHK_EXPECT_BINARY_LF:
2018 match = 0;
2019 tmp = alloc_trash_chunk();
2020 if (!tmp) {
2021 status = HCHK_STATUS_L7RSP;
2022 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002023 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002024 goto error;
2025 }
2026 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2027 if (!b_data(tmp)) {
2028 status = HCHK_STATUS_L7RSP;
2029 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002030 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002031 goto error;
2032 }
2033 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2034 int len = tmp->data;
2035 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2036 status = HCHK_STATUS_L7RSP;
2037 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002038 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002039 goto error;
2040 }
2041 tmp->data = len;
2042 }
2043 if (b_data(&check->bi) < tmp->data) {
2044 if (!last_read) {
2045 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002046 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002047 goto out;
2048 }
2049 break;
2050 }
2051 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2052 break;
2053
2054 case TCPCHK_EXPECT_CUSTOM:
2055 if (expect->custom)
2056 ret = expect->custom(check, rule, last_read);
2057 goto out;
2058 default:
2059 /* Should never happen. */
2060 ret = TCPCHK_EVAL_STOP;
2061 goto out;
2062 }
2063
2064
2065 /* Wait for more data on mismatch only if no minimum is defined (-1),
2066 * otherwise the absence of match is already conclusive.
2067 */
2068 if (!match && !last_read && (expect->min_recv == -1)) {
2069 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002070 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002071 goto out;
2072 }
2073
2074 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002075 if (match ^ inverse) {
2076 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002077 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002078 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002079
2080 error:
2081 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002082 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002083 ret = TCPCHK_EVAL_STOP;
2084 msg = alloc_trash_chunk();
2085 if (msg)
2086 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2087 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2088 free_trash_chunk(msg);
2089
2090 out:
2091 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002092 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002093 return ret;
2094}
2095
2096/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2097 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2098 * waits.
2099 */
2100enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2101{
2102 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2103 struct act_rule *act_rule;
2104 enum act_return act_ret;
2105
2106 act_rule =rule->action_kw.rule;
2107 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2108 if (act_ret != ACT_RET_CONT) {
2109 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2110 tcpcheck_get_step_id(check, rule));
2111 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2112 ret = TCPCHK_EVAL_STOP;
2113 }
2114
2115 return ret;
2116}
2117
2118/* Executes a tcp-check ruleset. Note that this is called both from the
2119 * connection's wake() callback and from the check scheduling task. It returns
2120 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2121 * presenting the risk of an fd replacement.
2122 *
2123 * Please do NOT place any return statement in this function and only leave
2124 * via the out_end_tcpcheck label after setting retcode.
2125 */
2126int tcpcheck_main(struct check *check)
2127{
2128 struct tcpcheck_rule *rule;
2129 struct conn_stream *cs = check->cs;
2130 struct connection *conn = cs_conn(cs);
2131 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002132 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002133 enum tcpcheck_eval_ret eval_ret;
2134
2135 /* here, we know that the check is complete or that it failed */
2136 if (check->result != CHK_RES_UNKNOWN)
2137 goto out;
2138
Christopher Faulet147b8c92021-04-10 09:00:38 +02002139 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2140
Willy Tarreau51cd5952020-06-05 12:25:38 +02002141 /* Note: the conn-stream and the connection may only be undefined before
2142 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002143 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002144 */
2145
2146 /* 1- check for connection error, if any */
2147 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2148 goto out_end_tcpcheck;
2149
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002150 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002151 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002152 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002153 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002154 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2155 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002156
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002157 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002158 * tcp-check variables */
2159 else {
2160 struct tcpcheck_var *var;
2161
2162 /* First evaluation, create a session */
2163 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2164 if (!check->sess) {
2165 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002166 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002167 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2168 goto out_end_tcpcheck;
2169 }
2170 vars_init(&check->vars, SCOPE_CHECK);
2171 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2172
2173 /* Preset tcp-check variables */
2174 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2175 struct sample smp;
2176
2177 memset(&smp, 0, sizeof(smp));
2178 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2179 smp.data = var->data;
2180 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2181 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002182 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002183 }
2184
2185 /* Now evaluate the tcp-check rules */
2186
2187 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2188 check->code = 0;
2189 switch (rule->action) {
2190 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002191 /* Not the first connection, release it first */
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002192 if (cs && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002193 check->state |= CHK_ST_CLOSE_CONN;
2194 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002195 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002196
2197 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002198
2199 /* We are still waiting the connection gets closed */
2200 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002201 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002202 eval_ret = TCPCHK_EVAL_WAIT;
2203 break;
2204 }
2205
Christopher Faulet147b8c92021-04-10 09:00:38 +02002206 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002207 eval_ret = tcpcheck_eval_connect(check, rule);
2208
2209 /* Refresh conn-stream and connection */
2210 cs = check->cs;
2211 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002212 last_read = 0;
2213 must_read = ((cs && IS_HTX_CS(cs)) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002214 break;
2215 case TCPCHK_ACT_SEND:
2216 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002217 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002218 eval_ret = tcpcheck_eval_send(check, rule);
2219 must_read = 1;
2220 break;
2221 case TCPCHK_ACT_EXPECT:
2222 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002223 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002224 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002225 eval_ret = tcpcheck_eval_recv(check, rule);
2226 if (eval_ret == TCPCHK_EVAL_STOP)
2227 goto out_end_tcpcheck;
2228 else if (eval_ret == TCPCHK_EVAL_WAIT)
2229 goto out;
2230 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2231 must_read = 0;
2232 }
2233
2234 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2235 ? tcpcheck_eval_expect_http(check, rule, last_read)
2236 : tcpcheck_eval_expect(check, rule, last_read));
2237
2238 if (eval_ret == TCPCHK_EVAL_WAIT) {
2239 check->current_step = rule->expect.head;
2240 if (!(check->wait_list.events & SUB_RETRY_RECV))
2241 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2242 }
2243 break;
2244 case TCPCHK_ACT_ACTION_KW:
2245 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002246 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002247 eval_ret = tcpcheck_eval_action_kw(check, rule);
2248 break;
2249 default:
2250 /* Otherwise, just go to the next one and don't update
2251 * the current step
2252 */
2253 eval_ret = TCPCHK_EVAL_CONTINUE;
2254 break;
2255 }
2256
2257 switch (eval_ret) {
2258 case TCPCHK_EVAL_CONTINUE:
2259 break;
2260 case TCPCHK_EVAL_WAIT:
2261 goto out;
2262 case TCPCHK_EVAL_STOP:
2263 goto out_end_tcpcheck;
2264 }
2265 }
2266
2267 /* All rules was evaluated */
2268 if (check->current_step) {
2269 rule = check->current_step;
2270
Christopher Faulet147b8c92021-04-10 09:00:38 +02002271 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2272
Willy Tarreau51cd5952020-06-05 12:25:38 +02002273 if (rule->action == TCPCHK_ACT_EXPECT) {
2274 struct buffer *msg;
2275 enum healthcheck_status status;
2276
2277 if (check->server &&
2278 (check->server->proxy->options & PR_O_DISABLE404) &&
2279 (check->server->next_state != SRV_ST_STOPPED) &&
2280 (check->code == 404)) {
2281 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002282 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002283 goto out_end_tcpcheck;
2284 }
2285
2286 msg = alloc_trash_chunk();
2287 if (msg)
2288 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2289 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2290 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2291 free_trash_chunk(msg);
2292 }
2293 else if (rule->action == TCPCHK_ACT_CONNECT) {
2294 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2295 enum healthcheck_status status = HCHK_STATUS_L4OK;
2296#ifdef USE_OPENSSL
2297 if (ssl_sock_is_ssl(conn))
2298 status = HCHK_STATUS_L6OK;
2299#endif
2300 set_server_check_status(check, status, msg);
2301 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002302 else
2303 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002304 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002305 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002306 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002307 }
2308 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002309
2310 out_end_tcpcheck:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002311 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) {
2312 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002313 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002314 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002315
Christopher Fauletb381a502020-11-25 13:47:00 +01002316 /* the tcpcheck is finished, release in/out buffer now */
2317 check_release_buf(check, &check->bi);
2318 check_release_buf(check, &check->bo);
2319
Willy Tarreau51cd5952020-06-05 12:25:38 +02002320 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002321 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002322 return retcode;
2323}
2324
2325
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
2343 actrule = calloc(1, sizeof(*actrule));
2344 if (!actrule) {
2345 memprintf(errmsg, "out of memory");
2346 goto error;
2347 }
2348 actrule->kw = kw;
2349 actrule->from = ACT_F_TCP_CHK;
2350
2351 cur_arg++;
2352 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2353 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2354 goto error;
2355 }
2356
2357 chk = calloc(1, sizeof(*chk));
2358 if (!chk) {
2359 memprintf(errmsg, "out of memory");
2360 goto error;
2361 }
2362 chk->action = TCPCHK_ACT_ACTION_KW;
2363 chk->action_kw.rule = actrule;
2364 return chk;
2365
2366 error:
2367 free(actrule);
2368 return NULL;
2369}
2370
2371/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2372 * returned on error.
2373 */
2374struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2375 const char *file, int line, char **errmsg)
2376{
2377 struct tcpcheck_rule *chk = NULL;
2378 struct sockaddr_storage *sk = NULL;
2379 char *comment = NULL, *sni = NULL, *alpn = NULL;
2380 struct sample_expr *port_expr = NULL;
2381 const struct mux_proto_list *mux_proto = NULL;
2382 unsigned short conn_opts = 0;
2383 long port = 0;
2384 int alpn_len = 0;
2385
2386 list_for_each_entry(chk, rules, list) {
2387 if (chk->action == TCPCHK_ACT_CONNECT)
2388 break;
2389 if (chk->action == TCPCHK_ACT_COMMENT ||
2390 chk->action == TCPCHK_ACT_ACTION_KW ||
2391 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2392 continue;
2393
2394 memprintf(errmsg, "first step MUST also be a 'connect', "
2395 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2396 "when there is a 'connect' step in the tcp-check ruleset");
2397 goto error;
2398 }
2399
2400 cur_arg++;
2401 while (*(args[cur_arg])) {
2402 if (strcmp(args[cur_arg], "default") == 0)
2403 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2404 else if (strcmp(args[cur_arg], "addr") == 0) {
2405 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002406
2407 if (!*(args[cur_arg+1])) {
2408 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2409 goto error;
2410 }
2411
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002412 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2413 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002414 if (!sk) {
2415 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2416 goto error;
2417 }
2418
Willy Tarreau51cd5952020-06-05 12:25:38 +02002419 cur_arg++;
2420 }
2421 else if (strcmp(args[cur_arg], "port") == 0) {
2422 const char *p, *end;
2423
2424 if (!*(args[cur_arg+1])) {
2425 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2426 goto error;
2427 }
2428 cur_arg++;
2429
2430 port = 0;
2431 release_sample_expr(port_expr);
2432 p = args[cur_arg]; end = p + strlen(p);
2433 port = read_uint(&p, end);
2434 if (p != end) {
2435 int idx = 0;
2436
2437 px->conf.args.ctx = ARGC_SRV;
2438 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet8551c342021-09-30 16:22:51 +02002439 file, line, errmsg, (px->cap & PR_CAP_DEF) ? NULL: &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002440
2441 if (!port_expr) {
2442 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2443 goto error;
2444 }
2445 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2446 memprintf(errmsg, "error detected while parsing port expression : "
2447 " fetch method '%s' extracts information from '%s', "
2448 "none of which is available here.\n",
2449 args[cur_arg], sample_src_names(port_expr->fetch->use));
2450 goto error;
2451 }
2452 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2453 }
2454 else if (port > 65535 || port < 1) {
2455 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2456 args[cur_arg]);
2457 goto error;
2458 }
2459 }
2460 else if (strcmp(args[cur_arg], "proto") == 0) {
2461 if (!*(args[cur_arg+1])) {
2462 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2463 goto error;
2464 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002465 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002466 if (!mux_proto) {
2467 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2468 goto error;
2469 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002470
2471 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2472 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2473 goto error;
2474 }
2475 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2476 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2477 goto error;
2478 }
2479
Willy Tarreau51cd5952020-06-05 12:25:38 +02002480 cur_arg++;
2481 }
2482 else if (strcmp(args[cur_arg], "comment") == 0) {
2483 if (!*(args[cur_arg+1])) {
2484 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2485 goto error;
2486 }
2487 cur_arg++;
2488 free(comment);
2489 comment = strdup(args[cur_arg]);
2490 if (!comment) {
2491 memprintf(errmsg, "out of memory");
2492 goto error;
2493 }
2494 }
2495 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2496 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2497 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2498 conn_opts |= TCPCHK_OPT_SOCKS4;
2499 else if (strcmp(args[cur_arg], "linger") == 0)
2500 conn_opts |= TCPCHK_OPT_LINGER;
2501#ifdef USE_OPENSSL
2502 else if (strcmp(args[cur_arg], "ssl") == 0) {
2503 px->options |= PR_O_TCPCHK_SSL;
2504 conn_opts |= TCPCHK_OPT_SSL;
2505 }
2506 else if (strcmp(args[cur_arg], "sni") == 0) {
2507 if (!*(args[cur_arg+1])) {
2508 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2509 goto error;
2510 }
2511 cur_arg++;
2512 free(sni);
2513 sni = strdup(args[cur_arg]);
2514 if (!sni) {
2515 memprintf(errmsg, "out of memory");
2516 goto error;
2517 }
2518 }
2519 else if (strcmp(args[cur_arg], "alpn") == 0) {
2520#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2521 free(alpn);
2522 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2523 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2524 goto error;
2525 }
2526 cur_arg++;
2527#else
2528 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2529 goto error;
2530#endif
2531 }
2532#endif /* USE_OPENSSL */
2533
2534 else {
2535 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2536#ifdef USE_OPENSSL
2537 ", 'ssl', 'sni', 'alpn'"
2538#endif /* USE_OPENSSL */
2539 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2540 args[cur_arg]);
2541 goto error;
2542 }
2543 cur_arg++;
2544 }
2545
2546 chk = calloc(1, sizeof(*chk));
2547 if (!chk) {
2548 memprintf(errmsg, "out of memory");
2549 goto error;
2550 }
2551 chk->action = TCPCHK_ACT_CONNECT;
2552 chk->comment = comment;
2553 chk->connect.port = port;
2554 chk->connect.options = conn_opts;
2555 chk->connect.sni = sni;
2556 chk->connect.alpn = alpn;
2557 chk->connect.alpn_len= alpn_len;
2558 chk->connect.port_expr= port_expr;
2559 chk->connect.mux_proto= mux_proto;
2560 if (sk)
2561 chk->connect.addr = *sk;
2562 return chk;
2563
2564 error:
2565 free(alpn);
2566 free(sni);
2567 free(comment);
2568 release_sample_expr(port_expr);
2569 return NULL;
2570}
2571
2572/* Parses and creates a tcp-check send rule. NULL is returned on error */
2573struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2574 const char *file, int line, char **errmsg)
2575{
2576 struct tcpcheck_rule *chk = NULL;
2577 char *comment = NULL, *data = NULL;
2578 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2579
2580 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2581 type = TCPCHK_SEND_BINARY_LF;
2582 else if (strcmp(args[cur_arg], "send-binary") == 0)
2583 type = TCPCHK_SEND_BINARY;
2584 else if (strcmp(args[cur_arg], "send-lf") == 0)
2585 type = TCPCHK_SEND_STRING_LF;
2586 else if (strcmp(args[cur_arg], "send") == 0)
2587 type = TCPCHK_SEND_STRING;
2588
2589 if (!*(args[cur_arg+1])) {
2590 memprintf(errmsg, "'%s' expects a %s as argument",
2591 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2592 goto error;
2593 }
2594
2595 data = args[cur_arg+1];
2596
2597 cur_arg += 2;
2598 while (*(args[cur_arg])) {
2599 if (strcmp(args[cur_arg], "comment") == 0) {
2600 if (!*(args[cur_arg+1])) {
2601 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2602 goto error;
2603 }
2604 cur_arg++;
2605 free(comment);
2606 comment = strdup(args[cur_arg]);
2607 if (!comment) {
2608 memprintf(errmsg, "out of memory");
2609 goto error;
2610 }
2611 }
2612 else {
2613 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2614 args[cur_arg]);
2615 goto error;
2616 }
2617 cur_arg++;
2618 }
2619
2620 chk = calloc(1, sizeof(*chk));
2621 if (!chk) {
2622 memprintf(errmsg, "out of memory");
2623 goto error;
2624 }
2625 chk->action = TCPCHK_ACT_SEND;
2626 chk->comment = comment;
2627 chk->send.type = type;
2628
2629 switch (chk->send.type) {
2630 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002631 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002632 if (!isttest(chk->send.data)) {
2633 memprintf(errmsg, "out of memory");
2634 goto error;
2635 }
2636 break;
2637 case TCPCHK_SEND_BINARY: {
2638 int len = chk->send.data.len;
2639 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2640 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2641 goto error;
2642 }
2643 chk->send.data.len = len;
2644 break;
2645 }
2646 case TCPCHK_SEND_STRING_LF:
2647 case TCPCHK_SEND_BINARY_LF:
2648 LIST_INIT(&chk->send.fmt);
2649 px->conf.args.ctx = ARGC_SRV;
2650 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2651 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2652 goto error;
2653 }
2654 break;
2655 case TCPCHK_SEND_HTTP:
2656 case TCPCHK_SEND_UNDEF:
2657 goto error;
2658 }
2659
2660 return chk;
2661
2662 error:
2663 free(chk);
2664 free(comment);
2665 return NULL;
2666}
2667
2668/* Parses and creates a http-check send rule. NULL is returned on error */
2669struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2670 const char *file, int line, char **errmsg)
2671{
2672 struct tcpcheck_rule *chk = NULL;
2673 struct tcpcheck_http_hdr *hdr = NULL;
2674 struct http_hdr hdrs[global.tune.max_http_hdr];
2675 char *meth = NULL, *uri = NULL, *vsn = NULL;
2676 char *body = NULL, *comment = NULL;
2677 unsigned int flags = 0;
2678 int i = 0, host_hdr = -1;
2679
2680 cur_arg++;
2681 while (*(args[cur_arg])) {
2682 if (strcmp(args[cur_arg], "meth") == 0) {
2683 if (!*(args[cur_arg+1])) {
2684 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2685 goto error;
2686 }
2687 cur_arg++;
2688 meth = args[cur_arg];
2689 }
2690 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2691 if (!*(args[cur_arg+1])) {
2692 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2693 goto error;
2694 }
2695 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2696 if (strcmp(args[cur_arg], "uri-lf") == 0)
2697 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2698 cur_arg++;
2699 uri = args[cur_arg];
2700 }
2701 else if (strcmp(args[cur_arg], "ver") == 0) {
2702 if (!*(args[cur_arg+1])) {
2703 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2704 goto error;
2705 }
2706 cur_arg++;
2707 vsn = args[cur_arg];
2708 }
2709 else if (strcmp(args[cur_arg], "hdr") == 0) {
2710 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2711 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2712 goto error;
2713 }
2714
2715 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2716 if (host_hdr >= 0) {
2717 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2718 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2719 goto error;
2720 }
2721 host_hdr = i;
2722 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002723 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002724 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2725 goto skip_hdr;
2726
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002727 hdrs[i].n = ist(args[cur_arg + 1]);
2728 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002729 i++;
2730 skip_hdr:
2731 cur_arg += 2;
2732 }
2733 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2734 if (!*(args[cur_arg+1])) {
2735 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2736 goto error;
2737 }
2738 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2739 if (strcmp(args[cur_arg], "body-lf") == 0)
2740 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2741 cur_arg++;
2742 body = args[cur_arg];
2743 }
2744 else if (strcmp(args[cur_arg], "comment") == 0) {
2745 if (!*(args[cur_arg+1])) {
2746 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2747 goto error;
2748 }
2749 cur_arg++;
2750 free(comment);
2751 comment = strdup(args[cur_arg]);
2752 if (!comment) {
2753 memprintf(errmsg, "out of memory");
2754 goto error;
2755 }
2756 }
2757 else {
2758 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2759 " but got '%s' as argument.", args[cur_arg]);
2760 goto error;
2761 }
2762 cur_arg++;
2763 }
2764
2765 hdrs[i].n = hdrs[i].v = IST_NULL;
2766
2767 chk = calloc(1, sizeof(*chk));
2768 if (!chk) {
2769 memprintf(errmsg, "out of memory");
2770 goto error;
2771 }
2772 chk->action = TCPCHK_ACT_SEND;
2773 chk->comment = comment; comment = NULL;
2774 chk->send.type = TCPCHK_SEND_HTTP;
2775 chk->send.http.flags = flags;
2776 LIST_INIT(&chk->send.http.hdrs);
2777
2778 if (meth) {
2779 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2780 chk->send.http.meth.str.area = strdup(meth);
2781 chk->send.http.meth.str.data = strlen(meth);
2782 if (!chk->send.http.meth.str.area) {
2783 memprintf(errmsg, "out of memory");
2784 goto error;
2785 }
2786 }
2787 if (uri) {
2788 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2789 LIST_INIT(&chk->send.http.uri_fmt);
2790 px->conf.args.ctx = ARGC_SRV;
2791 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2792 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2793 goto error;
2794 }
2795 }
2796 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002797 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002798 if (!isttest(chk->send.http.uri)) {
2799 memprintf(errmsg, "out of memory");
2800 goto error;
2801 }
2802 }
2803 }
2804 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002805 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002806 if (!isttest(chk->send.http.vsn)) {
2807 memprintf(errmsg, "out of memory");
2808 goto error;
2809 }
2810 }
2811 for (i = 0; istlen(hdrs[i].n); i++) {
2812 hdr = calloc(1, sizeof(*hdr));
2813 if (!hdr) {
2814 memprintf(errmsg, "out of memory");
2815 goto error;
2816 }
2817 LIST_INIT(&hdr->value);
2818 hdr->name = istdup(hdrs[i].n);
2819 if (!isttest(hdr->name)) {
2820 memprintf(errmsg, "out of memory");
2821 goto error;
2822 }
2823
2824 ist0(hdrs[i].v);
2825 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2826 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002827 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002828 hdr = NULL;
2829 }
2830
2831 if (body) {
2832 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2833 LIST_INIT(&chk->send.http.body_fmt);
2834 px->conf.args.ctx = ARGC_SRV;
2835 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2836 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2837 goto error;
2838 }
2839 }
2840 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002841 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002842 if (!isttest(chk->send.http.body)) {
2843 memprintf(errmsg, "out of memory");
2844 goto error;
2845 }
2846 }
2847 }
2848
2849 return chk;
2850
2851 error:
2852 free_tcpcheck_http_hdr(hdr);
2853 free_tcpcheck(chk, 0);
2854 free(comment);
2855 return NULL;
2856}
2857
2858/* Parses and creates a http-check comment rule. NULL is returned on error */
2859struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2860 const char *file, int line, char **errmsg)
2861{
2862 struct tcpcheck_rule *chk = NULL;
2863 char *comment = NULL;
2864
2865 if (!*(args[cur_arg+1])) {
2866 memprintf(errmsg, "expects a string as argument");
2867 goto error;
2868 }
2869 cur_arg++;
2870 comment = strdup(args[cur_arg]);
2871 if (!comment) {
2872 memprintf(errmsg, "out of memory");
2873 goto error;
2874 }
2875
2876 chk = calloc(1, sizeof(*chk));
2877 if (!chk) {
2878 memprintf(errmsg, "out of memory");
2879 goto error;
2880 }
2881 chk->action = TCPCHK_ACT_COMMENT;
2882 chk->comment = comment;
2883 return chk;
2884
2885 error:
2886 free(comment);
2887 return NULL;
2888}
2889
2890/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2891 * on error. <proto> is set to the right protocol flags (covered by the
2892 * TCPCHK_RULES_PROTO_CHK mask).
2893 */
2894struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2895 struct list *rules, unsigned int proto,
2896 const char *file, int line, char **errmsg)
2897{
2898 struct tcpcheck_rule *prev_check, *chk = NULL;
2899 struct sample_expr *status_expr = NULL;
2900 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2901 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2902 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2903 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2904 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2905 unsigned int flags = 0;
2906 long min_recv = -1;
2907 int inverse = 0;
2908
2909 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2910 if (!*(args[cur_arg+1])) {
2911 memprintf(errmsg, "expects at least a matching pattern as arguments");
2912 goto error;
2913 }
2914
2915 cur_arg++;
2916 while (*(args[cur_arg])) {
2917 int in_pattern = 0;
2918
2919 rescan:
2920 if (strcmp(args[cur_arg], "min-recv") == 0) {
2921 if (in_pattern) {
2922 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2923 goto error;
2924 }
2925 if (!*(args[cur_arg+1])) {
2926 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2927 goto error;
2928 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002929 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002930 cur_arg++;
2931 min_recv = atol(args[cur_arg]);
2932 if (min_recv < -1 || min_recv > INT_MAX) {
2933 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2934 goto error;
2935 }
2936 }
2937 else if (*(args[cur_arg]) == '!') {
2938 in_pattern = 1;
2939 while (*(args[cur_arg]) == '!') {
2940 inverse = !inverse;
2941 args[cur_arg]++;
2942 }
2943 if (!*(args[cur_arg]))
2944 cur_arg++;
2945 goto rescan;
2946 }
2947 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2948 if (type != TCPCHK_EXPECT_UNDEF) {
2949 memprintf(errmsg, "only on pattern expected");
2950 goto error;
2951 }
2952 if (proto != TCPCHK_RULES_HTTP_CHK)
2953 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2954 else
2955 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2956
2957 if (!*(args[cur_arg+1])) {
2958 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2959 goto error;
2960 }
2961 cur_arg++;
2962 pattern = args[cur_arg];
2963 }
2964 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2965 if (proto == TCPCHK_RULES_HTTP_CHK)
2966 goto bad_http_kw;
2967 if (type != TCPCHK_EXPECT_UNDEF) {
2968 memprintf(errmsg, "only on pattern expected");
2969 goto error;
2970 }
2971 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2972
2973 if (!*(args[cur_arg+1])) {
2974 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2975 goto error;
2976 }
2977 cur_arg++;
2978 pattern = args[cur_arg];
2979 }
2980 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2981 if (type != TCPCHK_EXPECT_UNDEF) {
2982 memprintf(errmsg, "only on pattern expected");
2983 goto error;
2984 }
2985 if (proto != TCPCHK_RULES_HTTP_CHK)
2986 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2987 else {
2988 if (*(args[cur_arg]) != 's')
2989 goto bad_http_kw;
2990 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2991 }
2992
2993 if (!*(args[cur_arg+1])) {
2994 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2995 goto error;
2996 }
2997 cur_arg++;
2998 pattern = args[cur_arg];
2999 }
3000 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3001 if (proto != TCPCHK_RULES_HTTP_CHK)
3002 goto bad_tcp_kw;
3003 if (type != TCPCHK_EXPECT_UNDEF) {
3004 memprintf(errmsg, "only on pattern expected");
3005 goto error;
3006 }
3007 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3008
3009 if (!*(args[cur_arg+1])) {
3010 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3011 goto error;
3012 }
3013 cur_arg++;
3014 pattern = args[cur_arg];
3015 }
3016 else if (strcmp(args[cur_arg], "custom") == 0) {
3017 if (in_pattern) {
3018 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3019 goto error;
3020 }
3021 if (type != TCPCHK_EXPECT_UNDEF) {
3022 memprintf(errmsg, "only on pattern expected");
3023 goto error;
3024 }
3025 type = TCPCHK_EXPECT_CUSTOM;
3026 }
3027 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3028 int orig_arg = cur_arg;
3029
3030 if (proto != TCPCHK_RULES_HTTP_CHK)
3031 goto bad_tcp_kw;
3032 if (type != TCPCHK_EXPECT_UNDEF) {
3033 memprintf(errmsg, "only on pattern expected");
3034 goto error;
3035 }
3036 type = TCPCHK_EXPECT_HTTP_HEADER;
3037
3038 if (strcmp(args[cur_arg], "fhdr") == 0)
3039 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3040
3041 /* Parse the name pattern, mandatory */
3042 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3043 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3044 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3045 args[orig_arg]);
3046 goto error;
3047 }
3048
3049 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3050 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3051
3052 cur_arg += 2;
3053 if (strcmp(args[cur_arg], "-m") == 0) {
3054 if (!*(args[cur_arg+1])) {
3055 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3056 args[orig_arg], args[cur_arg]);
3057 goto error;
3058 }
3059 if (strcmp(args[cur_arg+1], "str") == 0)
3060 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3061 else if (strcmp(args[cur_arg+1], "beg") == 0)
3062 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3063 else if (strcmp(args[cur_arg+1], "end") == 0)
3064 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3065 else if (strcmp(args[cur_arg+1], "sub") == 0)
3066 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3067 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3068 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3069 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3070 args[orig_arg]);
3071 goto error;
3072 }
3073 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3074 }
3075 else {
3076 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3077 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3078 goto error;
3079 }
3080 cur_arg += 2;
3081 }
3082 else
3083 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3084 npat = args[cur_arg];
3085
3086 if (!*(args[cur_arg+1]) ||
3087 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3088 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3089 goto next;
3090 }
3091 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3092 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3093
3094 /* Parse the value pattern, optional */
3095 if (strcmp(args[cur_arg+2], "-m") == 0) {
3096 cur_arg += 2;
3097 if (!*(args[cur_arg+1])) {
3098 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3099 args[orig_arg], args[cur_arg]);
3100 goto error;
3101 }
3102 if (strcmp(args[cur_arg+1], "str") == 0)
3103 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3104 else if (strcmp(args[cur_arg+1], "beg") == 0)
3105 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3106 else if (strcmp(args[cur_arg+1], "end") == 0)
3107 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3108 else if (strcmp(args[cur_arg+1], "sub") == 0)
3109 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3110 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3111 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3112 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3113 args[orig_arg]);
3114 goto error;
3115 }
3116 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3117 }
3118 else {
3119 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3120 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3121 goto error;
3122 }
3123 }
3124 else
3125 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3126
3127 if (!*(args[cur_arg+2])) {
3128 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3129 goto error;
3130 }
3131 vpat = args[cur_arg+2];
3132 cur_arg += 2;
3133 }
3134 else if (strcmp(args[cur_arg], "comment") == 0) {
3135 if (in_pattern) {
3136 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3137 goto error;
3138 }
3139 if (!*(args[cur_arg+1])) {
3140 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3141 goto error;
3142 }
3143 cur_arg++;
3144 free(comment);
3145 comment = strdup(args[cur_arg]);
3146 if (!comment) {
3147 memprintf(errmsg, "out of memory");
3148 goto error;
3149 }
3150 }
3151 else if (strcmp(args[cur_arg], "on-success") == 0) {
3152 if (in_pattern) {
3153 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3154 goto error;
3155 }
3156 if (!*(args[cur_arg+1])) {
3157 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3158 goto error;
3159 }
3160 cur_arg++;
3161 on_success_msg = args[cur_arg];
3162 }
3163 else if (strcmp(args[cur_arg], "on-error") == 0) {
3164 if (in_pattern) {
3165 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3166 goto error;
3167 }
3168 if (!*(args[cur_arg+1])) {
3169 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3170 goto error;
3171 }
3172 cur_arg++;
3173 on_error_msg = args[cur_arg];
3174 }
3175 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3176 if (in_pattern) {
3177 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3178 goto error;
3179 }
3180 if (!*(args[cur_arg+1])) {
3181 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3182 goto error;
3183 }
3184 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3185 ok_st = HCHK_STATUS_L7OKD;
3186 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3187 ok_st = HCHK_STATUS_L7OKCD;
3188 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3189 ok_st = HCHK_STATUS_L6OK;
3190 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3191 ok_st = HCHK_STATUS_L4OK;
3192 else {
3193 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3194 args[cur_arg], args[cur_arg+1]);
3195 goto error;
3196 }
3197 cur_arg++;
3198 }
3199 else if (strcmp(args[cur_arg], "error-status") == 0) {
3200 if (in_pattern) {
3201 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3202 goto error;
3203 }
3204 if (!*(args[cur_arg+1])) {
3205 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3206 goto error;
3207 }
3208 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3209 err_st = HCHK_STATUS_L7RSP;
3210 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3211 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003212 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3213 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003214 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3215 err_st = HCHK_STATUS_L6RSP;
3216 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3217 err_st = HCHK_STATUS_L4CON;
3218 else {
3219 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3220 args[cur_arg], args[cur_arg+1]);
3221 goto error;
3222 }
3223 cur_arg++;
3224 }
3225 else if (strcmp(args[cur_arg], "status-code") == 0) {
3226 int idx = 0;
3227
3228 if (in_pattern) {
3229 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3230 goto error;
3231 }
3232 if (!*(args[cur_arg+1])) {
3233 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3234 goto error;
3235 }
3236
3237 cur_arg++;
3238 release_sample_expr(status_expr);
3239 px->conf.args.ctx = ARGC_SRV;
3240 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet8551c342021-09-30 16:22:51 +02003241 file, line, errmsg, (px->cap & PR_CAP_DEF) ? NULL: &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003242 if (!status_expr) {
3243 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3244 goto error;
3245 }
3246 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3247 memprintf(errmsg, "error detected while parsing status-code expression : "
3248 " fetch method '%s' extracts information from '%s', "
3249 "none of which is available here.\n",
3250 args[cur_arg], sample_src_names(status_expr->fetch->use));
3251 goto error;
3252 }
3253 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3254 }
3255 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3256 if (in_pattern) {
3257 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3258 goto error;
3259 }
3260 if (!*(args[cur_arg+1])) {
3261 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3262 goto error;
3263 }
3264 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3265 tout_st = HCHK_STATUS_L7TOUT;
3266 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3267 tout_st = HCHK_STATUS_L6TOUT;
3268 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3269 tout_st = HCHK_STATUS_L4TOUT;
3270 else {
3271 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3272 args[cur_arg], args[cur_arg+1]);
3273 goto error;
3274 }
3275 cur_arg++;
3276 }
3277 else {
3278 if (proto == TCPCHK_RULES_HTTP_CHK) {
3279 bad_http_kw:
3280 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3281 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3282 }
3283 else {
3284 bad_tcp_kw:
3285 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3286 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3287 }
3288 goto error;
3289 }
3290 next:
3291 cur_arg++;
3292 }
3293
3294 chk = calloc(1, sizeof(*chk));
3295 if (!chk) {
3296 memprintf(errmsg, "out of memory");
3297 goto error;
3298 }
3299 chk->action = TCPCHK_ACT_EXPECT;
3300 LIST_INIT(&chk->expect.onerror_fmt);
3301 LIST_INIT(&chk->expect.onsuccess_fmt);
3302 chk->comment = comment; comment = NULL;
3303 chk->expect.type = type;
3304 chk->expect.min_recv = min_recv;
3305 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3306 chk->expect.ok_status = ok_st;
3307 chk->expect.err_status = err_st;
3308 chk->expect.tout_status = tout_st;
3309 chk->expect.status_expr = status_expr; status_expr = NULL;
3310
3311 if (on_success_msg) {
3312 px->conf.args.ctx = ARGC_SRV;
3313 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3314 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3315 goto error;
3316 }
3317 }
3318 if (on_error_msg) {
3319 px->conf.args.ctx = ARGC_SRV;
3320 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3321 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3322 goto error;
3323 }
3324 }
3325
3326 switch (chk->expect.type) {
3327 case TCPCHK_EXPECT_HTTP_STATUS: {
3328 const char *p = pattern;
3329 unsigned int c1,c2;
3330
3331 chk->expect.codes.codes = NULL;
3332 chk->expect.codes.num = 0;
3333 while (1) {
3334 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3335 if (*p == '-') {
3336 p++;
3337 c2 = read_uint(&p, pattern + strlen(pattern));
3338 }
3339 if (c1 > c2) {
3340 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3341 goto error;
3342 }
3343
3344 chk->expect.codes.num++;
3345 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3346 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3347 if (!chk->expect.codes.codes) {
3348 memprintf(errmsg, "out of memory");
3349 goto error;
3350 }
3351 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3352 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3353
3354 if (*p == '\0')
3355 break;
3356 if (*p != ',') {
3357 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3358 goto error;
3359 }
3360 p++;
3361 }
3362 break;
3363 }
3364 case TCPCHK_EXPECT_STRING:
3365 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003366 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003367 if (!isttest(chk->expect.data)) {
3368 memprintf(errmsg, "out of memory");
3369 goto error;
3370 }
3371 break;
3372 case TCPCHK_EXPECT_BINARY: {
3373 int len = chk->expect.data.len;
3374
3375 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3376 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3377 goto error;
3378 }
3379 chk->expect.data.len = len;
3380 break;
3381 }
3382 case TCPCHK_EXPECT_STRING_REGEX:
3383 case TCPCHK_EXPECT_BINARY_REGEX:
3384 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3385 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3386 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3387 if (!chk->expect.regex)
3388 goto error;
3389 break;
3390
3391 case TCPCHK_EXPECT_STRING_LF:
3392 case TCPCHK_EXPECT_BINARY_LF:
3393 case TCPCHK_EXPECT_HTTP_BODY_LF:
3394 LIST_INIT(&chk->expect.fmt);
3395 px->conf.args.ctx = ARGC_SRV;
3396 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3397 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3398 goto error;
3399 }
3400 break;
3401
3402 case TCPCHK_EXPECT_HTTP_HEADER:
3403 if (!npat) {
3404 memprintf(errmsg, "unexpected error, undefined header name pattern");
3405 goto error;
3406 }
3407 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3408 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3409 if (!chk->expect.hdr.name_re)
3410 goto error;
3411 }
3412 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3413 px->conf.args.ctx = ARGC_SRV;
3414 LIST_INIT(&chk->expect.hdr.name_fmt);
3415 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3416 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3417 goto error;
3418 }
3419 }
3420 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003421 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003422 if (!isttest(chk->expect.hdr.name)) {
3423 memprintf(errmsg, "out of memory");
3424 goto error;
3425 }
3426 }
3427
3428 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3429 chk->expect.hdr.value = IST_NULL;
3430 break;
3431 }
3432
3433 if (!vpat) {
3434 memprintf(errmsg, "unexpected error, undefined header value pattern");
3435 goto error;
3436 }
3437 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3438 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3439 if (!chk->expect.hdr.value_re)
3440 goto error;
3441 }
3442 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3443 px->conf.args.ctx = ARGC_SRV;
3444 LIST_INIT(&chk->expect.hdr.value_fmt);
3445 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3446 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3447 goto error;
3448 }
3449 }
3450 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003451 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003452 if (!isttest(chk->expect.hdr.value)) {
3453 memprintf(errmsg, "out of memory");
3454 goto error;
3455 }
3456 }
3457
3458 break;
3459 case TCPCHK_EXPECT_CUSTOM:
3460 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3461 break;
3462 case TCPCHK_EXPECT_UNDEF:
3463 memprintf(errmsg, "pattern not found");
3464 goto error;
3465 }
3466
3467 /* All tcp-check expect points back to the first inverse expect rule in
3468 * a chain of one or more expect rule, potentially itself.
3469 */
3470 chk->expect.head = chk;
3471 list_for_each_entry_rev(prev_check, rules, list) {
3472 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3473 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3474 chk->expect.head = prev_check;
3475 continue;
3476 }
3477 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3478 break;
3479 }
3480 return chk;
3481
3482 error:
3483 free_tcpcheck(chk, 0);
3484 free(comment);
3485 release_sample_expr(status_expr);
3486 return NULL;
3487}
3488
3489/* Overwrites fields of the old http send rule with those of the new one. When
3490 * replaced, old values are freed and replaced by the new ones. New values are
3491 * not copied but transferred. At the end <new> should be empty and can be
3492 * safely released. This function never fails.
3493 */
3494void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3495{
3496 struct logformat_node *lf, *lfb;
3497 struct tcpcheck_http_hdr *hdr, *bhdr;
3498
3499
3500 if (new->send.http.meth.str.area) {
3501 free(old->send.http.meth.str.area);
3502 old->send.http.meth.meth = new->send.http.meth.meth;
3503 old->send.http.meth.str.area = new->send.http.meth.str.area;
3504 old->send.http.meth.str.data = new->send.http.meth.str.data;
3505 new->send.http.meth.str = BUF_NULL;
3506 }
3507
3508 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3509 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3510 istfree(&old->send.http.uri);
3511 else
3512 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3513 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3514 old->send.http.uri = new->send.http.uri;
3515 new->send.http.uri = IST_NULL;
3516 }
3517 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3518 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3519 istfree(&old->send.http.uri);
3520 else
3521 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3522 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3523 LIST_INIT(&old->send.http.uri_fmt);
3524 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003525 LIST_DELETE(&lf->list);
3526 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003527 }
3528 }
3529
3530 if (isttest(new->send.http.vsn)) {
3531 istfree(&old->send.http.vsn);
3532 old->send.http.vsn = new->send.http.vsn;
3533 new->send.http.vsn = IST_NULL;
3534 }
3535
Christopher Faulet94cd9a42022-07-05 15:33:53 +02003536 if (!LIST_ISEMPTY(&new->send.http.hdrs)) {
3537 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3538 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
3539 LIST_DELETE(&hdr->list);
3540 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
3541 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02003542 }
3543
3544 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3545 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3546 istfree(&old->send.http.body);
3547 else
3548 free_tcpcheck_fmt(&old->send.http.body_fmt);
3549 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3550 old->send.http.body = new->send.http.body;
3551 new->send.http.body = IST_NULL;
3552 }
3553 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3554 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3555 istfree(&old->send.http.body);
3556 else
3557 free_tcpcheck_fmt(&old->send.http.body_fmt);
3558 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3559 LIST_INIT(&old->send.http.body_fmt);
3560 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003561 LIST_DELETE(&lf->list);
3562 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003563 }
3564 }
3565}
3566
3567/* Internal function used to add an http-check rule in a list during the config
3568 * parsing step. Depending on its type, and the previously inserted rules, a
3569 * specific action may be performed or an error may be reported. This functions
3570 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3571 * message.
3572 */
3573int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3574{
3575 struct tcpcheck_rule *r;
3576
3577 /* the implicit send rule coming from an "option httpchk" line must be
3578 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003579 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003580 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003581 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003582 * sure the ruleset remains valid.
3583 */
3584
3585 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3586 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3587 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3588 * following tests are performed :
3589 *
3590 * 1- If there is no such rule or if it is not a send rule, the implicit send
3591 * rule is pushed in front of the ruleset
3592 *
3593 * 2- If it is another implicit send rule, it is replaced with the new one.
3594 *
3595 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3596 * both, overwriting the old send rule (the explicit one) with info of the
3597 * new send rule (the implicit one).
3598 */
3599 r = get_first_tcpcheck_rule(rules);
3600 if (r && r->action == TCPCHK_ACT_CONNECT)
3601 r = get_next_tcpcheck_rule(rules, r);
3602 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003603 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003604 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003605 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003606 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003607 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003608 }
3609 else {
3610 tcpcheck_overwrite_send_http_rule(r, chk);
3611 free_tcpcheck(chk, 0);
3612 }
3613 }
3614 else {
3615 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3616 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3617 * with an existing implicit send rule, if any. At the end, if there is no error,
3618 * the rule is appended to the list.
3619 */
3620
3621 r = get_last_tcpcheck_rule(rules);
3622 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3623 /* no error */;
3624 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3625 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3626 chk->index+1);
3627 return 0;
3628 }
3629 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3630 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3631 chk->index+1);
3632 return 0;
3633 }
3634 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3635 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3636 chk->index+1);
3637 return 0;
3638 }
3639
3640 if (chk->action == TCPCHK_ACT_SEND) {
3641 r = get_first_tcpcheck_rule(rules);
3642 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3643 tcpcheck_overwrite_send_http_rule(r, chk);
3644 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003645 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003646 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3647 chk = r;
3648 }
3649 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003650 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003651 }
3652 return 1;
3653}
3654
3655/* Check tcp-check health-check configuration for the proxy <px>. */
3656static int check_proxy_tcpcheck(struct proxy *px)
3657{
3658 struct tcpcheck_rule *chk, *back;
3659 char *comment = NULL, *errmsg = NULL;
3660 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003661 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003662
3663 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3664 deinit_proxy_tcpcheck(px);
3665 goto out;
3666 }
3667
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003668 ha_free(&px->check_command);
3669 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003670
3671 if (!px->tcpcheck_rules.list) {
3672 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
3673 ret |= ERR_ALERT | ERR_FATAL;
3674 goto out;
3675 }
3676
3677 /* HTTP ruleset only : */
3678 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3679 struct tcpcheck_rule *next;
3680
3681 /* move remaining implicit send rule from "option httpchk" line to the right place.
3682 * If such rule exists, it must be the first one. In this case, the rule is moved
3683 * after the first connect rule, if any. Otherwise, nothing is done.
3684 */
3685 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3686 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3687 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3688 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003689 LIST_DELETE(&chk->list);
3690 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003691 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003692 }
3693 }
3694
3695 /* add implicit expect rule if the last one is a send. It is inherited from previous
3696 * versions where the http expect rule was optional. Now it is possible to chained
3697 * send/expect rules but the last expect may still be implicit.
3698 */
3699 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3700 if (chk && chk->action == TCPCHK_ACT_SEND) {
3701 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3702 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3703 px->conf.file, px->conf.line, &errmsg);
3704 if (!next) {
3705 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
3706 "(%s).\n", px->id, errmsg);
3707 free(errmsg);
3708 ret |= ERR_ALERT | ERR_FATAL;
3709 goto out;
3710 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003711 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003712 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003713 }
3714 }
3715
3716 /* For all ruleset: */
3717
3718 /* If there is no connect rule preceding all send / expect rules, an
3719 * implicit one is inserted before all others.
3720 */
3721 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3722 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3723 chk = calloc(1, sizeof(*chk));
3724 if (!chk) {
3725 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
3726 "(out of memory).\n", px->id);
3727 ret |= ERR_ALERT | ERR_FATAL;
3728 goto out;
3729 }
3730 chk->action = TCPCHK_ACT_CONNECT;
3731 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003732 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003733 }
3734
3735 /* Remove all comment rules. To do so, when a such rule is found, the
3736 * comment is assigned to the following rule(s).
3737 */
3738 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Faulet8af4ab82022-08-24 11:38:03 +02003739 struct tcpcheck_rule *next;
3740
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003741 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3742 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003743
3744 prev_action = chk->action;
3745 switch (chk->action) {
3746 case TCPCHK_ACT_COMMENT:
3747 free(comment);
3748 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003749 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003750 free(chk);
3751 break;
3752 case TCPCHK_ACT_CONNECT:
3753 if (!chk->comment && comment)
3754 chk->comment = strdup(comment);
Christopher Faulet8af4ab82022-08-24 11:38:03 +02003755 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3756 if (next && next->action == TCPCHK_ACT_SEND)
3757 chk->connect.options |= TCPCHK_OPT_HAS_DATA;
Tim Duesterhus588b3142020-05-29 14:35:51 +02003758 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003759 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003760 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003761 break;
3762 case TCPCHK_ACT_SEND:
3763 case TCPCHK_ACT_EXPECT:
3764 if (!chk->comment && comment)
3765 chk->comment = strdup(comment);
3766 break;
3767 }
3768 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003769 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003770
3771 out:
3772 return ret;
3773}
3774
3775void deinit_proxy_tcpcheck(struct proxy *px)
3776{
3777 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3778 px->tcpcheck_rules.flags = 0;
3779 px->tcpcheck_rules.list = NULL;
3780}
3781
3782static void deinit_tcpchecks()
3783{
3784 struct tcpcheck_ruleset *rs;
3785 struct tcpcheck_rule *r, *rb;
3786 struct ebpt_node *node, *next;
3787
3788 node = ebpt_first(&shared_tcpchecks);
3789 while (node) {
3790 next = ebpt_next(node);
3791 ebpt_delete(node);
3792 free(node->key);
3793 rs = container_of(node, typeof(*rs), node);
3794 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003795 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003796 free_tcpcheck(r, 0);
3797 }
3798 free(rs);
3799 node = next;
3800 }
3801}
3802
3803int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3804{
3805 struct tcpcheck_rule *tcpcheck, *prev_check;
3806 struct tcpcheck_expect *expect;
3807
Willy Tarreau6922e552021-03-22 21:11:45 +01003808 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003809 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003810 tcpcheck->action = TCPCHK_ACT_EXPECT;
3811
3812 expect = &tcpcheck->expect;
3813 expect->type = TCPCHK_EXPECT_STRING;
3814 LIST_INIT(&expect->onerror_fmt);
3815 LIST_INIT(&expect->onsuccess_fmt);
3816 expect->ok_status = HCHK_STATUS_L7OKD;
3817 expect->err_status = HCHK_STATUS_L7RSP;
3818 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003819 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003820 if (!isttest(expect->data)) {
3821 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3822 return 0;
3823 }
3824
3825 /* All tcp-check expect points back to the first inverse expect rule
3826 * in a chain of one or more expect rule, potentially itself.
3827 */
3828 tcpcheck->expect.head = tcpcheck;
3829 list_for_each_entry_rev(prev_check, rules->list, list) {
3830 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3831 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3832 tcpcheck->expect.head = prev_check;
3833 continue;
3834 }
3835 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3836 break;
3837 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003838 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003839 return 1;
3840}
3841
3842int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3843{
3844 struct tcpcheck_rule *tcpcheck;
3845 struct tcpcheck_send *send;
3846 const char *in;
3847 char *dst;
3848 int i;
3849
Willy Tarreau6922e552021-03-22 21:11:45 +01003850 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003851 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003852 tcpcheck->action = TCPCHK_ACT_SEND;
3853
3854 send = &tcpcheck->send;
3855 send->type = TCPCHK_SEND_STRING;
3856
3857 for (i = 0; strs[i]; i++)
3858 send->data.len += strlen(strs[i]);
3859
3860 send->data.ptr = malloc(istlen(send->data) + 1);
3861 if (!isttest(send->data)) {
3862 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3863 return 0;
3864 }
3865
3866 dst = istptr(send->data);
3867 for (i = 0; strs[i]; i++)
3868 for (in = strs[i]; (*dst = *in++); dst++);
3869 *dst = 0;
3870
Willy Tarreau2b718102021-04-21 07:32:39 +02003871 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003872 return 1;
3873}
3874
3875/* Parses the "tcp-check" proxy keyword */
3876static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003877 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003878 char **errmsg)
3879{
3880 struct tcpcheck_ruleset *rs = NULL;
3881 struct tcpcheck_rule *chk = NULL;
3882 int index, cur_arg, ret = 0;
3883
3884 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3885 ret = 1;
3886
3887 /* Deduce the ruleset name from the proxy info */
3888 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3889 ((curpx == defpx) ? "defaults" : curpx->id),
3890 curpx->conf.file, curpx->conf.line);
3891
3892 rs = find_tcpcheck_ruleset(b_orig(&trash));
3893 if (rs == NULL) {
3894 rs = create_tcpcheck_ruleset(b_orig(&trash));
3895 if (rs == NULL) {
3896 memprintf(errmsg, "out of memory.\n");
3897 goto error;
3898 }
3899 }
3900
3901 index = 0;
3902 if (!LIST_ISEMPTY(&rs->rules)) {
3903 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3904 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003905 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003906 }
3907
3908 cur_arg = 1;
3909 if (strcmp(args[cur_arg], "connect") == 0)
3910 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3911 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3912 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3913 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3914 else if (strcmp(args[cur_arg], "expect") == 0)
3915 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3916 else if (strcmp(args[cur_arg], "comment") == 0)
3917 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3918 else {
3919 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3920
3921 if (!kw) {
3922 action_kw_tcp_check_build_list(&trash);
3923 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3924 "%s%s. but got '%s'",
3925 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3926 goto error;
3927 }
3928 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3929 }
3930
3931 if (!chk) {
3932 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3933 goto error;
3934 }
3935 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3936
3937 /* No error: add the tcp-check rule in the list */
3938 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003939 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003940
3941 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3942 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3943 /* Use this ruleset if the proxy already has tcp-check enabled */
3944 curpx->tcpcheck_rules.list = &rs->rules;
3945 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3946 }
3947 else {
3948 /* mark this ruleset as unused for now */
3949 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3950 }
3951
3952 return ret;
3953
3954 error:
3955 free_tcpcheck(chk, 0);
3956 free_tcpcheck_ruleset(rs);
3957 return -1;
3958}
3959
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003960/* Parses the "http-check" proxy keyword */
3961static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003962 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003963 char **errmsg)
3964{
3965 struct tcpcheck_ruleset *rs = NULL;
3966 struct tcpcheck_rule *chk = NULL;
3967 int index, cur_arg, ret = 0;
3968
3969 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3970 ret = 1;
3971
3972 cur_arg = 1;
3973 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3974 /* enable a graceful server shutdown on an HTTP 404 response */
3975 curpx->options |= PR_O_DISABLE404;
3976 if (too_many_args(1, args, errmsg, NULL))
3977 goto error;
3978 goto out;
3979 }
3980 else if (strcmp(args[cur_arg], "send-state") == 0) {
3981 /* enable emission of the apparent state of a server in HTTP checks */
3982 curpx->options2 |= PR_O2_CHK_SNDST;
3983 if (too_many_args(1, args, errmsg, NULL))
3984 goto error;
3985 goto out;
3986 }
3987
3988 /* Deduce the ruleset name from the proxy info */
3989 chunk_printf(&trash, "*http-check-%s_%s-%d",
3990 ((curpx == defpx) ? "defaults" : curpx->id),
3991 curpx->conf.file, curpx->conf.line);
3992
3993 rs = find_tcpcheck_ruleset(b_orig(&trash));
3994 if (rs == NULL) {
3995 rs = create_tcpcheck_ruleset(b_orig(&trash));
3996 if (rs == NULL) {
3997 memprintf(errmsg, "out of memory.\n");
3998 goto error;
3999 }
4000 }
4001
4002 index = 0;
4003 if (!LIST_ISEMPTY(&rs->rules)) {
4004 chk = LIST_PREV(&rs->rules, typeof(chk), list);
4005 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4006 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004007 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004008 }
4009
4010 if (strcmp(args[cur_arg], "connect") == 0)
4011 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4012 else if (strcmp(args[cur_arg], "send") == 0)
4013 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4014 else if (strcmp(args[cur_arg], "expect") == 0)
4015 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4016 file, line, errmsg);
4017 else if (strcmp(args[cur_arg], "comment") == 0)
4018 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4019 else {
4020 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4021
4022 if (!kw) {
4023 action_kw_tcp_check_build_list(&trash);
4024 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4025 " 'send', 'expect'%s%s. but got '%s'",
4026 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4027 goto error;
4028 }
4029 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4030 }
4031
4032 if (!chk) {
4033 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4034 goto error;
4035 }
4036 ret = (*errmsg != NULL); /* Handle warning */
4037
4038 chk->index = index;
4039 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4040 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4041 /* Use this ruleset if the proxy already has http-check enabled */
4042 curpx->tcpcheck_rules.list = &rs->rules;
4043 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4044 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4045 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4046 curpx->tcpcheck_rules.list = NULL;
4047 goto error;
4048 }
4049 }
4050 else {
4051 /* mark this ruleset as unused for now */
4052 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004053 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004054 }
4055
4056 out:
4057 return ret;
4058
4059 error:
4060 free_tcpcheck(chk, 0);
4061 free_tcpcheck_ruleset(rs);
4062 return -1;
4063}
4064
4065/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004066int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004067 const char *file, int line)
4068{
4069 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4070 static char *redis_res = "+PONG\r\n";
4071
4072 struct tcpcheck_ruleset *rs = NULL;
4073 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4074 struct tcpcheck_rule *chk;
4075 char *errmsg = NULL;
4076 int err_code = 0;
4077
4078 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4079 err_code |= ERR_WARN;
4080
4081 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4082 goto out;
4083
4084 curpx->options2 &= ~PR_O2_CHK_ANY;
4085 curpx->options2 |= PR_O2_TCPCHK_CHK;
4086
4087 free_tcpcheck_vars(&rules->preset_vars);
4088 rules->list = NULL;
4089 rules->flags = 0;
4090
4091 rs = find_tcpcheck_ruleset("*redis-check");
4092 if (rs)
4093 goto ruleset_found;
4094
4095 rs = create_tcpcheck_ruleset("*redis-check");
4096 if (rs == NULL) {
4097 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4098 goto error;
4099 }
4100
4101 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4102 1, curpx, &rs->rules, file, line, &errmsg);
4103 if (!chk) {
4104 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4105 goto error;
4106 }
4107 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004108 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004109
4110 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4111 "error-status", "L7STS",
4112 "on-error", "%[res.payload(0,0),cut_crlf]",
4113 "on-success", "Redis server is ok",
4114 ""},
4115 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4116 if (!chk) {
4117 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4118 goto error;
4119 }
4120 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004121 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004122
4123 ruleset_found:
4124 rules->list = &rs->rules;
4125 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4126 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4127
4128 out:
4129 free(errmsg);
4130 return err_code;
4131
4132 error:
4133 free_tcpcheck_ruleset(rs);
4134 err_code |= ERR_ALERT | ERR_FATAL;
4135 goto out;
4136}
4137
4138
4139/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004140int 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 +01004141 const char *file, int line)
4142{
4143 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4144 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4145 *
4146 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4147 */
4148 static char sslv3_client_hello[] = {
4149 "16" /* ContentType : 0x16 = Handshake */
4150 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4151 "0079" /* ContentLength : 0x79 bytes after this one */
4152 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4153 "000075" /* HandshakeLength : 0x75 bytes after this one */
4154 "0300" /* Hello Version : 0x0300 = v3 */
4155 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4156 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4157 "00" /* Session ID length : empty (no session ID) */
4158 "004E" /* Cipher Suite Length : 78 bytes after this one */
4159 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4160 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4161 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4162 "000D" "000E" "000F" "0010" /* various bit lengths, */
4163 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4164 "0015" "0016" "0017" "0018"
4165 "0019" "001A" "001B" "002F"
4166 "0030" "0031" "0032" "0033"
4167 "0034" "0035" "0036" "0037"
4168 "0038" "0039" "003A"
4169 "01" /* Compression Length : 0x01 = 1 byte for types */
4170 "00" /* Compression Type : 0x00 = NULL compression */
4171 };
4172
4173 struct tcpcheck_ruleset *rs = NULL;
4174 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4175 struct tcpcheck_rule *chk;
4176 char *errmsg = NULL;
4177 int err_code = 0;
4178
4179 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4180 err_code |= ERR_WARN;
4181
4182 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4183 goto out;
4184
4185 curpx->options2 &= ~PR_O2_CHK_ANY;
4186 curpx->options2 |= PR_O2_TCPCHK_CHK;
4187
4188 free_tcpcheck_vars(&rules->preset_vars);
4189 rules->list = NULL;
4190 rules->flags = 0;
4191
4192 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4193 if (rs)
4194 goto ruleset_found;
4195
4196 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4197 if (rs == NULL) {
4198 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4199 goto error;
4200 }
4201
4202 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4203 1, curpx, &rs->rules, file, line, &errmsg);
4204 if (!chk) {
4205 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4206 goto error;
4207 }
4208 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004209 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004210
4211 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4212 "min-recv", "5", "ok-status", "L6OK",
4213 "error-status", "L6RSP", "tout-status", "L6TOUT",
4214 ""},
4215 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4216 if (!chk) {
4217 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4218 goto error;
4219 }
4220 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004221 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004222
4223 ruleset_found:
4224 rules->list = &rs->rules;
4225 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4226 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4227
4228 out:
4229 free(errmsg);
4230 return err_code;
4231
4232 error:
4233 free_tcpcheck_ruleset(rs);
4234 err_code |= ERR_ALERT | ERR_FATAL;
4235 goto out;
4236}
4237
4238/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004239int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004240 const char *file, int line)
4241{
4242 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4243
4244 struct tcpcheck_ruleset *rs = NULL;
4245 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4246 struct tcpcheck_rule *chk;
4247 struct tcpcheck_var *var = NULL;
4248 char *cmd = NULL, *errmsg = NULL;
4249 int err_code = 0;
4250
4251 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4252 err_code |= ERR_WARN;
4253
4254 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4255 goto out;
4256
4257 curpx->options2 &= ~PR_O2_CHK_ANY;
4258 curpx->options2 |= PR_O2_TCPCHK_CHK;
4259
4260 free_tcpcheck_vars(&rules->preset_vars);
4261 rules->list = NULL;
4262 rules->flags = 0;
4263
4264 cur_arg += 2;
4265 if (*args[cur_arg] && *args[cur_arg+1] &&
4266 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4267 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4268 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4269 if (cmd)
4270 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4271 }
4272 else {
4273 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4274 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4275 cmd = strdup("HELO localhost");
4276 }
4277
4278 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4279 if (cmd == NULL || var == NULL) {
4280 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4281 goto error;
4282 }
4283 var->data.type = SMP_T_STR;
4284 var->data.u.str.area = cmd;
4285 var->data.u.str.data = strlen(cmd);
4286 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004287 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004288 cmd = NULL;
4289 var = NULL;
4290
4291 rs = find_tcpcheck_ruleset("*smtp-check");
4292 if (rs)
4293 goto ruleset_found;
4294
4295 rs = create_tcpcheck_ruleset("*smtp-check");
4296 if (rs == NULL) {
4297 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4298 goto error;
4299 }
4300
4301 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4302 1, curpx, &rs->rules, file, line, &errmsg);
4303 if (!chk) {
4304 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4305 goto error;
4306 }
4307 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004308 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004309
4310 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4311 "min-recv", "4",
4312 "error-status", "L7RSP",
4313 "on-error", "%[res.payload(0,0),cut_crlf]",
4314 ""},
4315 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4316 if (!chk) {
4317 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4318 goto error;
4319 }
4320 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004321 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004322
4323 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4324 "min-recv", "4",
4325 "error-status", "L7STS",
4326 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4327 "status-code", "res.payload(0,3)",
4328 ""},
4329 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4330 if (!chk) {
4331 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4332 goto error;
4333 }
4334 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004335 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004336
4337 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4338 1, curpx, &rs->rules, file, line, &errmsg);
4339 if (!chk) {
4340 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4341 goto error;
4342 }
4343 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004344 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004345
4346 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4347 "min-recv", "4",
4348 "error-status", "L7STS",
4349 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4350 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4351 "status-code", "res.payload(0,3)",
4352 ""},
4353 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4354 if (!chk) {
4355 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4356 goto error;
4357 }
4358 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004359 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004360
4361 ruleset_found:
4362 rules->list = &rs->rules;
4363 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4364 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4365
4366 out:
4367 free(errmsg);
4368 return err_code;
4369
4370 error:
4371 free(cmd);
4372 free(var);
4373 free_tcpcheck_vars(&rules->preset_vars);
4374 free_tcpcheck_ruleset(rs);
4375 err_code |= ERR_ALERT | ERR_FATAL;
4376 goto out;
4377}
4378
4379/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004380int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004381 const char *file, int line)
4382{
4383 static char pgsql_req[] = {
4384 "%[var(check.plen),htonl,hex]" /* The packet length*/
4385 "00030000" /* the version 3.0 */
4386 "7573657200" /* "user" key */
4387 "%[var(check.username),hex]00" /* the username */
4388 "00"
4389 };
4390
4391 struct tcpcheck_ruleset *rs = NULL;
4392 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4393 struct tcpcheck_rule *chk;
4394 struct tcpcheck_var *var = NULL;
4395 char *user = NULL, *errmsg = NULL;
4396 size_t packetlen = 0;
4397 int err_code = 0;
4398
4399 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4400 err_code |= ERR_WARN;
4401
4402 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4403 goto out;
4404
4405 curpx->options2 &= ~PR_O2_CHK_ANY;
4406 curpx->options2 |= PR_O2_TCPCHK_CHK;
4407
4408 free_tcpcheck_vars(&rules->preset_vars);
4409 rules->list = NULL;
4410 rules->flags = 0;
4411
4412 cur_arg += 2;
4413 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4414 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4415 file, line, args[0], args[1]);
4416 goto error;
4417 }
4418 if (strcmp(args[cur_arg], "user") == 0) {
4419 packetlen = 15 + strlen(args[cur_arg+1]);
4420 user = strdup(args[cur_arg+1]);
4421
4422 var = create_tcpcheck_var(ist("check.username"));
4423 if (user == NULL || var == NULL) {
4424 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4425 goto error;
4426 }
4427 var->data.type = SMP_T_STR;
4428 var->data.u.str.area = user;
4429 var->data.u.str.data = strlen(user);
4430 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004431 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004432 user = NULL;
4433 var = NULL;
4434
4435 var = create_tcpcheck_var(ist("check.plen"));
4436 if (var == NULL) {
4437 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4438 goto error;
4439 }
4440 var->data.type = SMP_T_SINT;
4441 var->data.u.sint = packetlen;
4442 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004443 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004444 var = NULL;
4445 }
4446 else {
4447 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4448 file, line, args[0], args[1]);
4449 goto error;
4450 }
4451
4452 rs = find_tcpcheck_ruleset("*pgsql-check");
4453 if (rs)
4454 goto ruleset_found;
4455
4456 rs = create_tcpcheck_ruleset("*pgsql-check");
4457 if (rs == NULL) {
4458 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4459 goto error;
4460 }
4461
4462 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4463 1, curpx, &rs->rules, file, line, &errmsg);
4464 if (!chk) {
4465 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4466 goto error;
4467 }
4468 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004469 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004470
4471 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4472 1, curpx, &rs->rules, file, line, &errmsg);
4473 if (!chk) {
4474 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4475 goto error;
4476 }
4477 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004478 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004479
4480 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4481 "min-recv", "5",
4482 "error-status", "L7RSP",
4483 "on-error", "%[res.payload(6,0)]",
4484 ""},
4485 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4486 if (!chk) {
4487 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4488 goto error;
4489 }
4490 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004491 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004492
4493 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4494 "min-recv", "9",
4495 "error-status", "L7STS",
4496 "on-success", "PostgreSQL server is ok",
4497 "on-error", "PostgreSQL unknown error",
4498 ""},
4499 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4500 if (!chk) {
4501 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4502 goto error;
4503 }
4504 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004505 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004506
4507 ruleset_found:
4508 rules->list = &rs->rules;
4509 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4510 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4511
4512 out:
4513 free(errmsg);
4514 return err_code;
4515
4516 error:
4517 free(user);
4518 free(var);
4519 free_tcpcheck_vars(&rules->preset_vars);
4520 free_tcpcheck_ruleset(rs);
4521 err_code |= ERR_ALERT | ERR_FATAL;
4522 goto out;
4523}
4524
4525
4526/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004527int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004528 const char *file, int line)
4529{
4530 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4531 * const char mysql40_client_auth_pkt[] = {
4532 * "\x0e\x00\x00" // packet length
4533 * "\x01" // packet number
4534 * "\x00\x00" // client capabilities
4535 * "\x00\x00\x01" // max packet
4536 * "haproxy\x00" // username (null terminated string)
4537 * "\x00" // filler (always 0x00)
4538 * "\x01\x00\x00" // packet length
4539 * "\x00" // packet number
4540 * "\x01" // COM_QUIT command
4541 * };
4542 */
4543 static char mysql40_rsname[] = "*mysql40-check";
4544 static char mysql40_req[] = {
4545 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4546 "0080" /* client capabilities */
4547 "000001" /* max packet */
4548 "%[var(check.username),hex]00" /* the username */
4549 "00" /* filler (always 0x00) */
4550 "010000" /* packet length*/
4551 "00" /* sequence ID */
4552 "01" /* COM_QUIT command */
4553 };
4554
4555 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4556 * const char mysql41_client_auth_pkt[] = {
4557 * "\x0e\x00\x00\" // packet length
4558 * "\x01" // packet number
4559 * "\x00\x00\x00\x00" // client capabilities
4560 * "\x00\x00\x00\x01" // max packet
4561 * "\x21" // character set (UTF-8)
4562 * char[23] // All zeroes
4563 * "haproxy\x00" // username (null terminated string)
4564 * "\x00" // filler (always 0x00)
4565 * "\x01\x00\x00" // packet length
4566 * "\x00" // packet number
4567 * "\x01" // COM_QUIT command
4568 * };
4569 */
4570 static char mysql41_rsname[] = "*mysql41-check";
4571 static char mysql41_req[] = {
4572 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4573 "00820000" /* client capabilities */
4574 "00800001" /* max packet */
4575 "21" /* character set (UTF-8) */
4576 "000000000000000000000000" /* 23 bytes, al zeroes */
4577 "0000000000000000000000"
4578 "%[var(check.username),hex]00" /* the username */
4579 "00" /* filler (always 0x00) */
4580 "010000" /* packet length*/
4581 "00" /* sequence ID */
4582 "01" /* COM_QUIT command */
4583 };
4584
4585 struct tcpcheck_ruleset *rs = NULL;
4586 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4587 struct tcpcheck_rule *chk;
4588 struct tcpcheck_var *var = NULL;
4589 char *mysql_rsname = "*mysql-check";
4590 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4591 int index = 0, err_code = 0;
4592
4593 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4594 err_code |= ERR_WARN;
4595
4596 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4597 goto out;
4598
4599 curpx->options2 &= ~PR_O2_CHK_ANY;
4600 curpx->options2 |= PR_O2_TCPCHK_CHK;
4601
4602 free_tcpcheck_vars(&rules->preset_vars);
4603 rules->list = NULL;
4604 rules->flags = 0;
4605
4606 cur_arg += 2;
4607 if (*args[cur_arg]) {
4608 int packetlen, userlen;
4609
4610 if (strcmp(args[cur_arg], "user") != 0) {
4611 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4612 file, line, args[0], args[1], args[cur_arg]);
4613 goto error;
4614 }
4615
4616 if (*(args[cur_arg+1]) == 0) {
4617 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4618 file, line, args[0], args[1], args[cur_arg]);
4619 goto error;
4620 }
4621
4622 hdr = calloc(4, sizeof(*hdr));
4623 user = strdup(args[cur_arg+1]);
4624 userlen = strlen(args[cur_arg+1]);
4625
4626 if (hdr == NULL || user == NULL) {
4627 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4628 goto error;
4629 }
4630
4631 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4632 packetlen = userlen + 7 + 27;
4633 mysql_req = mysql41_req;
4634 mysql_rsname = mysql41_rsname;
4635 }
4636 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4637 packetlen = userlen + 7;
4638 mysql_req = mysql40_req;
4639 mysql_rsname = mysql40_rsname;
4640 }
4641 else {
4642 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4643 file, line, args[cur_arg], args[cur_arg+2]);
4644 goto error;
4645 }
4646
4647 hdr[0] = (unsigned char)(packetlen & 0xff);
4648 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4649 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4650 hdr[3] = 1;
4651
4652 var = create_tcpcheck_var(ist("check.header"));
4653 if (var == NULL) {
4654 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4655 goto error;
4656 }
4657 var->data.type = SMP_T_STR;
4658 var->data.u.str.area = hdr;
4659 var->data.u.str.data = 4;
4660 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004661 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004662 hdr = NULL;
4663 var = NULL;
4664
4665 var = create_tcpcheck_var(ist("check.username"));
4666 if (var == NULL) {
4667 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4668 goto error;
4669 }
4670 var->data.type = SMP_T_STR;
4671 var->data.u.str.area = user;
4672 var->data.u.str.data = strlen(user);
4673 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004674 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004675 user = NULL;
4676 var = NULL;
4677 }
4678
4679 rs = find_tcpcheck_ruleset(mysql_rsname);
4680 if (rs)
4681 goto ruleset_found;
4682
4683 rs = create_tcpcheck_ruleset(mysql_rsname);
4684 if (rs == NULL) {
4685 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4686 goto error;
4687 }
4688
4689 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4690 1, curpx, &rs->rules, file, line, &errmsg);
4691 if (!chk) {
4692 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4693 goto error;
4694 }
4695 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004696 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004697
4698 if (mysql_req) {
4699 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4700 1, curpx, &rs->rules, file, line, &errmsg);
4701 if (!chk) {
4702 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4703 goto error;
4704 }
4705 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004706 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004707 }
4708
4709 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4710 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4711 if (!chk) {
4712 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4713 goto error;
4714 }
4715 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4716 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004717 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004718
4719 if (mysql_req) {
4720 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4721 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4722 if (!chk) {
4723 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4724 goto error;
4725 }
4726 chk->expect.custom = tcpcheck_mysql_expect_ok;
4727 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004728 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004729 }
4730
4731 ruleset_found:
4732 rules->list = &rs->rules;
4733 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4734 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4735
4736 out:
4737 free(errmsg);
4738 return err_code;
4739
4740 error:
4741 free(hdr);
4742 free(user);
4743 free(var);
4744 free_tcpcheck_vars(&rules->preset_vars);
4745 free_tcpcheck_ruleset(rs);
4746 err_code |= ERR_ALERT | ERR_FATAL;
4747 goto out;
4748}
4749
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004750int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004751 const char *file, int line)
4752{
4753 static char *ldap_req = "300C020101600702010304008000";
4754
4755 struct tcpcheck_ruleset *rs = NULL;
4756 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4757 struct tcpcheck_rule *chk;
4758 char *errmsg = NULL;
4759 int err_code = 0;
4760
4761 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4762 err_code |= ERR_WARN;
4763
4764 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4765 goto out;
4766
4767 curpx->options2 &= ~PR_O2_CHK_ANY;
4768 curpx->options2 |= PR_O2_TCPCHK_CHK;
4769
4770 free_tcpcheck_vars(&rules->preset_vars);
4771 rules->list = NULL;
4772 rules->flags = 0;
4773
4774 rs = find_tcpcheck_ruleset("*ldap-check");
4775 if (rs)
4776 goto ruleset_found;
4777
4778 rs = create_tcpcheck_ruleset("*ldap-check");
4779 if (rs == NULL) {
4780 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4781 goto error;
4782 }
4783
4784 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4785 1, curpx, &rs->rules, file, line, &errmsg);
4786 if (!chk) {
4787 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4788 goto error;
4789 }
4790 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004791 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004792
4793 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4794 "min-recv", "14",
4795 "on-error", "Not LDAPv3 protocol",
4796 ""},
4797 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4798 if (!chk) {
4799 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4800 goto error;
4801 }
4802 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004803 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004804
4805 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4806 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4807 if (!chk) {
4808 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4809 goto error;
4810 }
4811 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4812 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004813 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004814
4815 ruleset_found:
4816 rules->list = &rs->rules;
4817 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4818 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4819
4820 out:
4821 free(errmsg);
4822 return err_code;
4823
4824 error:
4825 free_tcpcheck_ruleset(rs);
4826 err_code |= ERR_ALERT | ERR_FATAL;
4827 goto out;
4828}
4829
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004830int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004831 const char *file, int line)
4832{
4833 struct tcpcheck_ruleset *rs = NULL;
4834 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4835 struct tcpcheck_rule *chk;
4836 char *spop_req = NULL;
4837 char *errmsg = NULL;
4838 int spop_len = 0, err_code = 0;
4839
4840 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4841 err_code |= ERR_WARN;
4842
4843 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4844 goto out;
4845
4846 curpx->options2 &= ~PR_O2_CHK_ANY;
4847 curpx->options2 |= PR_O2_TCPCHK_CHK;
4848
4849 free_tcpcheck_vars(&rules->preset_vars);
4850 rules->list = NULL;
4851 rules->flags = 0;
4852
4853
4854 rs = find_tcpcheck_ruleset("*spop-check");
4855 if (rs)
4856 goto ruleset_found;
4857
4858 rs = create_tcpcheck_ruleset("*spop-check");
4859 if (rs == NULL) {
4860 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4861 goto error;
4862 }
4863
4864 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4865 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4866 goto error;
4867 }
4868 chunk_reset(&trash);
4869 dump_binary(&trash, spop_req, spop_len);
4870 trash.area[trash.data] = '\0';
4871
4872 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4873 1, curpx, &rs->rules, file, line, &errmsg);
4874 if (!chk) {
4875 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4876 goto error;
4877 }
4878 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004879 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004880
4881 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4882 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4883 if (!chk) {
4884 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4885 goto error;
4886 }
4887 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4888 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004889 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004890
4891 ruleset_found:
4892 rules->list = &rs->rules;
4893 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4894 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4895
4896 out:
4897 free(spop_req);
4898 free(errmsg);
4899 return err_code;
4900
4901 error:
4902 free_tcpcheck_ruleset(rs);
4903 err_code |= ERR_ALERT | ERR_FATAL;
4904 goto out;
4905}
4906
4907
4908static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4909{
4910 struct tcpcheck_rule *chk = NULL;
4911 struct tcpcheck_http_hdr *hdr = NULL;
4912 char *meth = NULL, *uri = NULL, *vsn = NULL;
4913 char *hdrs, *body;
4914
4915 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4916 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4917 if (hdrs == body)
4918 hdrs = NULL;
4919 if (hdrs) {
4920 *hdrs = '\0';
4921 hdrs +=2;
4922 }
4923 if (body) {
4924 *body = '\0';
4925 body += 4;
4926 }
4927 if (hdrs || body) {
4928 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4929 " Please, consider to use 'http-check send' directive instead.");
4930 }
4931
4932 chk = calloc(1, sizeof(*chk));
4933 if (!chk) {
4934 memprintf(errmsg, "out of memory");
4935 goto error;
4936 }
4937 chk->action = TCPCHK_ACT_SEND;
4938 chk->send.type = TCPCHK_SEND_HTTP;
4939 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4940 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4941 LIST_INIT(&chk->send.http.hdrs);
4942
4943 /* Copy the method, uri and version */
4944 if (*args[cur_arg]) {
4945 if (!*args[cur_arg+1])
4946 uri = args[cur_arg];
4947 else
4948 meth = args[cur_arg];
4949 }
4950 if (*args[cur_arg+1])
4951 uri = args[cur_arg+1];
4952 if (*args[cur_arg+2])
4953 vsn = args[cur_arg+2];
4954
4955 if (meth) {
4956 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4957 chk->send.http.meth.str.area = strdup(meth);
4958 chk->send.http.meth.str.data = strlen(meth);
4959 if (!chk->send.http.meth.str.area) {
4960 memprintf(errmsg, "out of memory");
4961 goto error;
4962 }
4963 }
4964 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004965 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004966 if (!isttest(chk->send.http.uri)) {
4967 memprintf(errmsg, "out of memory");
4968 goto error;
4969 }
4970 }
4971 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004972 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004973 if (!isttest(chk->send.http.vsn)) {
4974 memprintf(errmsg, "out of memory");
4975 goto error;
4976 }
4977 }
4978
4979 /* Copy the header */
4980 if (hdrs) {
4981 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4982 struct h1m h1m;
4983 int i, ret;
4984
4985 /* Build and parse the request */
4986 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4987
4988 h1m.flags = H1_MF_HDRS_ONLY;
4989 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4990 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4991 &h1m, NULL);
4992 if (ret <= 0) {
4993 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4994 goto error;
4995 }
4996
4997 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4998 hdr = calloc(1, sizeof(*hdr));
4999 if (!hdr) {
5000 memprintf(errmsg, "out of memory");
5001 goto error;
5002 }
5003 LIST_INIT(&hdr->value);
5004 hdr->name = istdup(tmp_hdrs[i].n);
5005 if (!hdr->name.ptr) {
5006 memprintf(errmsg, "out of memory");
5007 goto error;
5008 }
5009
5010 ist0(tmp_hdrs[i].v);
5011 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5012 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005013 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005014 }
5015 }
5016
5017 /* Copy the body */
5018 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005019 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005020 if (!isttest(chk->send.http.body)) {
5021 memprintf(errmsg, "out of memory");
5022 goto error;
5023 }
5024 }
5025
5026 return chk;
5027
5028 error:
5029 free_tcpcheck_http_hdr(hdr);
5030 free_tcpcheck(chk, 0);
5031 return NULL;
5032}
5033
5034/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005035int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005036 const char *file, int line)
5037{
5038 struct tcpcheck_ruleset *rs = NULL;
5039 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5040 struct tcpcheck_rule *chk;
5041 char *errmsg = NULL;
5042 int err_code = 0;
5043
5044 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5045 err_code |= ERR_WARN;
5046
5047 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5048 goto out;
5049
5050 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5051 if (!chk) {
5052 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5053 goto error;
5054 }
5055 if (errmsg) {
5056 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5057 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005058 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005059 }
5060
5061 no_request:
5062 curpx->options2 &= ~PR_O2_CHK_ANY;
5063 curpx->options2 |= PR_O2_TCPCHK_CHK;
5064
5065 free_tcpcheck_vars(&rules->preset_vars);
5066 rules->list = NULL;
5067 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5068
5069 /* Deduce the ruleset name from the proxy info */
5070 chunk_printf(&trash, "*http-check-%s_%s-%d",
5071 ((curpx == defpx) ? "defaults" : curpx->id),
5072 curpx->conf.file, curpx->conf.line);
5073
5074 rs = find_tcpcheck_ruleset(b_orig(&trash));
5075 if (rs == NULL) {
5076 rs = create_tcpcheck_ruleset(b_orig(&trash));
5077 if (rs == NULL) {
5078 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5079 goto error;
5080 }
5081 }
5082
5083 rules->list = &rs->rules;
5084 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5085 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5086 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5087 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5088 rules->list = NULL;
5089 goto error;
5090 }
5091
5092 out:
5093 free(errmsg);
5094 return err_code;
5095
5096 error:
5097 free_tcpcheck_ruleset(rs);
5098 free_tcpcheck(chk, 0);
5099 err_code |= ERR_ALERT | ERR_FATAL;
5100 goto out;
5101}
5102
5103/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005104int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005105 const char *file, int line)
5106{
5107 struct tcpcheck_ruleset *rs = NULL;
5108 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5109 int err_code = 0;
5110
5111 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5112 err_code |= ERR_WARN;
5113
5114 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5115 goto out;
5116
5117 curpx->options2 &= ~PR_O2_CHK_ANY;
5118 curpx->options2 |= PR_O2_TCPCHK_CHK;
5119
5120 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5121 /* If a tcp-check rulesset is already set, do nothing */
5122 if (rules->list)
5123 goto out;
5124
5125 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5126 * get it.
5127 */
5128 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5129 goto curpx_ruleset;
5130
5131 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5132 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5133 rs = find_tcpcheck_ruleset(b_orig(&trash));
5134 if (rs)
5135 goto ruleset_found;
5136 }
5137
5138 curpx_ruleset:
5139 /* Deduce the ruleset name from the proxy info */
5140 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5141 ((curpx == defpx) ? "defaults" : curpx->id),
5142 curpx->conf.file, curpx->conf.line);
5143
5144 rs = find_tcpcheck_ruleset(b_orig(&trash));
5145 if (rs == NULL) {
5146 rs = create_tcpcheck_ruleset(b_orig(&trash));
5147 if (rs == NULL) {
5148 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5149 goto error;
5150 }
5151 }
5152
5153 ruleset_found:
5154 free_tcpcheck_vars(&rules->preset_vars);
5155 rules->list = &rs->rules;
5156 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5157 rules->flags |= TCPCHK_RULES_TCP_CHK;
5158
5159 out:
5160 return err_code;
5161
5162 error:
5163 err_code |= ERR_ALERT | ERR_FATAL;
5164 goto out;
5165}
5166
Willy Tarreau51cd5952020-06-05 12:25:38 +02005167static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005168 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005169 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5170 { 0, NULL, NULL },
5171}};
5172
5173REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5174REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5175REGISTER_POST_DEINIT(deinit_tcpchecks);
5176INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);