blob: d79ad47852238fdbd6bda170ace571dc48c26626 [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 Faulet8af4ab82022-08-24 11:38:03 +02001193 if (connect->options & TCPCHK_OPT_HAS_DATA)
1194 flags = (CONNECT_HAS_DATA|CONNECT_DELACK_ALWAYS);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001195 status = proto->connect(conn, flags);
1196 }
1197
1198 if (status != SF_ERR_NONE)
1199 goto fail_check;
1200
Christopher Faulet21ddc742020-07-01 15:26:14 +02001201 conn_set_private(conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001202 conn->ctx = cs;
1203
Willy Tarreau51cd5952020-06-05 12:25:38 +02001204#ifdef USE_OPENSSL
1205 if (connect->sni)
1206 ssl_sock_set_servername(conn, connect->sni);
1207 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1208 ssl_sock_set_servername(conn, s->check.sni);
1209
1210 if (connect->alpn)
1211 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1212 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1213 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1214#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001215
1216 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1217 /* Some servers don't like reset on close */
Willy Tarreaub41a6e92021-04-06 17:49:19 +02001218 HA_ATOMIC_AND(&fdtab[cs->conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001219 }
1220
1221 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1222 if (xprt_add_hs(conn) < 0)
1223 status = SF_ERR_RESOURCE;
1224 }
1225
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001226 if (conn_xprt_start(conn) < 0) {
1227 status = SF_ERR_RESOURCE;
1228 goto fail_check;
1229 }
1230
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001231 /* The mux may be initialized now if there isn't server attached to the
1232 * check (email alerts) or if there is a mux proto specified or if there
1233 * is no alpn.
1234 */
1235 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1236 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1237 const struct mux_ops *mux_ops;
1238
Christopher Faulet147b8c92021-04-10 09:00:38 +02001239 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001240 if (connect->mux_proto)
1241 mux_ops = connect->mux_proto->mux;
1242 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1243 mux_ops = check->mux_proto->mux;
1244 else {
1245 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1246 ? PROTO_MODE_HTTP
1247 : PROTO_MODE_TCP);
1248
1249 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1250 }
1251 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001252 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001253 status = SF_ERR_INTERNAL;
1254 goto fail_check;
1255 }
1256 }
1257
Willy Tarreau51cd5952020-06-05 12:25:38 +02001258 fail_check:
1259 /* It can return one of :
1260 * - SF_ERR_NONE if everything's OK
1261 * - SF_ERR_SRVTO if there are no more servers
1262 * - SF_ERR_SRVCL if the connection was refused by the server
1263 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1264 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1265 * - SF_ERR_INTERNAL for any other purely internal errors
1266 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1267 * Note that we try to prevent the network stack from sending the ACK during the
1268 * connect() when a pure TCP check is used (without PROXY protocol).
1269 */
1270 switch (status) {
1271 case SF_ERR_NONE:
1272 /* we allow up to min(inter, timeout.connect) for a connection
1273 * to establish but only when timeout.check is set as it may be
1274 * to short for a full check otherwise
1275 */
1276 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1277
1278 if (proxy->timeout.check && proxy->timeout.connect) {
1279 int t_con = tick_add(now_ms, proxy->timeout.connect);
1280 t->expire = tick_first(t->expire, t_con);
1281 }
1282 break;
1283 case SF_ERR_SRVTO: /* ETIMEDOUT */
1284 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1285 case SF_ERR_PRXCOND:
1286 case SF_ERR_RESOURCE:
1287 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001288 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 +02001289 chk_report_conn_err(check, errno, 0);
1290 ret = TCPCHK_EVAL_STOP;
1291 goto out;
1292 }
1293
1294 /* don't do anything until the connection is established */
1295 if (conn->flags & CO_FL_WAIT_XPRT) {
1296 if (conn->mux) {
1297 if (next && next->action == TCPCHK_ACT_SEND)
1298 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1299 else
1300 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
1301 }
1302 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001303 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001304 goto out;
1305 }
1306
1307 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001308 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001309 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001310 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001311 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001312
1313 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1314 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1315
Christopher Faulet147b8c92021-04-10 09:00:38 +02001316 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001317 return ret;
1318}
1319
1320/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1321 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1322 * TCPCHK_EVAL_STOP if an error occurred.
1323 */
1324enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1325{
1326 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1327 struct tcpcheck_send *send = &rule->send;
1328 struct conn_stream *cs = check->cs;
1329 struct connection *conn = cs_conn(cs);
1330 struct buffer *tmp = NULL;
1331 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001332 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001333
Christopher Faulet147b8c92021-04-10 09:00:38 +02001334 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1335
Christopher Fauletb381a502020-11-25 13:47:00 +01001336 if (check->state & CHK_ST_OUT_ALLOC) {
1337 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001338 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 +01001339 goto out;
1340 }
1341
1342 if (!check_get_buf(check, &check->bo)) {
1343 check->state |= CHK_ST_OUT_ALLOC;
1344 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001345 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 +01001346 goto out;
1347 }
1348
Christopher Faulet39066c22020-11-25 13:34:51 +01001349 /* Data already pending in the output buffer, send them now */
Christopher Faulet18280ca2021-08-11 15:46:29 +02001350 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 +02001351 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 +01001352 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001353 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001354
Christopher Fauletb381a502020-11-25 13:47:00 +01001355 /* Always release input buffer when a new send is evaluated */
1356 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001357
1358 switch (send->type) {
1359 case TCPCHK_SEND_STRING:
1360 case TCPCHK_SEND_BINARY:
1361 if (istlen(send->data) >= b_size(&check->bo)) {
1362 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1363 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1364 tcpcheck_get_step_id(check, rule));
1365 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1366 ret = TCPCHK_EVAL_STOP;
1367 goto out;
1368 }
1369 b_putist(&check->bo, send->data);
1370 break;
1371 case TCPCHK_SEND_STRING_LF:
1372 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1373 if (!b_data(&check->bo))
1374 goto out;
1375 break;
1376 case TCPCHK_SEND_BINARY_LF: {
1377 int len = b_size(&check->bo);
1378
1379 tmp = alloc_trash_chunk();
1380 if (!tmp)
1381 goto error_lf;
1382 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1383 if (!b_data(tmp))
1384 goto out;
1385 tmp->area[tmp->data] = '\0';
1386 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1387 goto error_lf;
1388 check->bo.data = len;
1389 break;
1390 }
1391 case TCPCHK_SEND_HTTP: {
1392 struct htx_sl *sl;
1393 struct ist meth, uri, vsn, clen, body;
1394 unsigned int slflags = 0;
1395
1396 tmp = alloc_trash_chunk();
1397 if (!tmp)
1398 goto error_htx;
1399
1400 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1401 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1402 : http_known_methods[send->http.meth.meth]);
1403 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1404 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1405 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1406 }
1407 else
1408 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1409 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1410
1411 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1412 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1413 slflags |= HTX_SL_F_VER_11;
1414 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1415 if (!isttest(send->http.body))
1416 slflags |= HTX_SL_F_BODYLESS;
1417
1418 htx = htx_from_buf(&check->bo);
1419 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1420 if (!sl)
1421 goto error_htx;
1422 sl->info.req.meth = send->http.meth.meth;
1423 if (!http_update_host(htx, sl, uri))
1424 goto error_htx;
1425
1426 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1427 struct tcpcheck_http_hdr *hdr;
1428 struct ist hdr_value;
1429
1430 list_for_each_entry(hdr, &send->http.hdrs, list) {
1431 chunk_reset(tmp);
1432 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1433 if (!b_data(tmp))
1434 continue;
1435 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1436 if (!htx_add_header(htx, hdr->name, hdr_value))
1437 goto error_htx;
1438 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1439 if (!http_update_authority(htx, sl, hdr_value))
1440 goto error_htx;
1441 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001442 if (isteqi(hdr->name, ist("connection")))
1443 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001444 }
1445
1446 }
1447 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1448 chunk_reset(tmp);
1449 httpchk_build_status_header(check->server, tmp);
1450 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1451 goto error_htx;
1452 }
1453
1454
1455 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1456 chunk_reset(tmp);
1457 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1458 body = ist2(b_orig(tmp), b_data(tmp));
1459 }
1460 else
1461 body = send->http.body;
1462 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1463
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001464 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001465 !htx_add_header(htx, ist("Content-length"), clen))
1466 goto error_htx;
1467
1468
1469 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001470 (istlen(body) && !htx_add_data_atonce(htx, body)))
1471 goto error_htx;
1472
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001473 /* no more data are expected */
1474 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001475 htx_to_buf(htx, &check->bo);
1476 break;
1477 }
1478 case TCPCHK_SEND_UNDEF:
1479 /* Should never happen. */
1480 ret = TCPCHK_EVAL_STOP;
1481 goto out;
1482 };
1483
Christopher Faulet39066c22020-11-25 13:34:51 +01001484 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001485 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001486 if (conn->mux->snd_buf(cs, &check->bo,
1487 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1488 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1489 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001490 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 +02001491 goto out;
1492 }
1493 }
Christopher Faulet18280ca2021-08-11 15:46:29 +02001494 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 +02001495 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1496 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001497 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001498 goto out;
1499 }
1500
1501 out:
1502 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001503 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1504 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001505
1506 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001507 return ret;
1508
1509 error_htx:
1510 if (htx) {
1511 htx_reset(htx);
1512 htx_to_buf(htx, &check->bo);
1513 }
1514 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1515 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001516 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 +02001517 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1518 ret = TCPCHK_EVAL_STOP;
1519 goto out;
1520
1521 error_lf:
1522 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1523 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001524 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 +02001525 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1526 ret = TCPCHK_EVAL_STOP;
1527 goto out;
1528
1529}
1530
1531/* Try to receive data before evaluating a tcp-check expect rule. Returns
1532 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1533 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1534 * TCPCHK_EVAL_STOP if an error occurred.
1535 */
1536enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1537{
1538 struct conn_stream *cs = check->cs;
1539 struct connection *conn = cs_conn(cs);
1540 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1541 size_t max, read, cur_read = 0;
1542 int is_empty;
1543 int read_poll = MAX_READ_POLL_LOOPS;
1544
Christopher Faulet147b8c92021-04-10 09:00:38 +02001545 TRACE_ENTER(CHK_EV_RX_DATA, check);
1546
1547 if (check->wait_list.events & SUB_RETRY_RECV) {
1548 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001549 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001550 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001551
1552 if (cs->flags & CS_FL_EOS)
1553 goto end_recv;
1554
Christopher Faulet147b8c92021-04-10 09:00:38 +02001555 if (check->state & CHK_ST_IN_ALLOC) {
1556 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001557 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001558 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001559
1560 if (!check_get_buf(check, &check->bi)) {
1561 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001562 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001563 goto wait_more_data;
1564 }
1565
Willy Tarreau51cd5952020-06-05 12:25:38 +02001566 /* errors on the connection and the conn-stream were already checked */
1567
1568 /* prepare to detect if the mux needs more room */
1569 cs->flags &= ~CS_FL_WANT_ROOM;
1570
1571 while ((cs->flags & CS_FL_RCV_MORE) ||
1572 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1573 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1574 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1575 cur_read += read;
1576 if (!read ||
1577 (cs->flags & CS_FL_WANT_ROOM) ||
1578 (--read_poll <= 0) ||
1579 (read < max && read >= global.tune.recv_enough))
1580 break;
1581 }
1582
1583 end_recv:
1584 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1585 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1586 /* Report network errors only if we got no other data. Otherwise
1587 * we'll let the upper layers decide whether the response is OK
1588 * or not. It is very common that an RST sent by the server is
1589 * reported as an error just after the last data chunk.
1590 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001591 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001592 goto stop;
1593 }
1594 if (!cur_read) {
Christopher Fauletbd017472021-10-20 13:53:38 +02001595 if (cs->flags & CS_FL_EOI) {
1596 /* If EOI is set, it means there is a response or an error */
1597 goto out;
1598 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001599 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1600 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001601 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001602 goto wait_more_data;
1603 }
1604 if (is_empty) {
1605 int status;
1606
1607 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1608 tcpcheck_get_step_id(check, rule));
1609 if (rule->comment)
1610 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1611
Christopher Faulet147b8c92021-04-10 09:00:38 +02001612 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001613 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1614 set_server_check_status(check, status, trash.area);
1615 goto stop;
1616 }
1617 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001618 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001619
1620 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001621 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1622 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001623
1624 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001625 return ret;
1626
1627 stop:
1628 ret = TCPCHK_EVAL_STOP;
1629 goto out;
1630
1631 wait_more_data:
1632 ret = TCPCHK_EVAL_WAIT;
1633 goto out;
1634}
1635
1636/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1637 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1638 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1639 * error occurred.
1640 */
1641enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1642{
1643 struct htx *htx = htxbuf(&check->bi);
1644 struct htx_sl *sl;
1645 struct htx_blk *blk;
1646 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1647 struct tcpcheck_expect *expect = &rule->expect;
1648 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1649 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1650 struct ist desc = IST_NULL;
1651 int i, match, inverse;
1652
Christopher Faulet147b8c92021-04-10 09:00:38 +02001653 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1654
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001655 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001656
1657 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001658 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001659 status = HCHK_STATUS_L7RSP;
1660 goto error;
1661 }
1662
1663 if (htx_is_empty(htx)) {
1664 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001665 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001666 status = HCHK_STATUS_L7RSP;
1667 goto error;
1668 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001669 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001670 goto wait_more_data;
1671 }
1672
1673 sl = http_get_stline(htx);
1674 check->code = sl->info.res.status;
1675
1676 if (check->server &&
1677 (check->server->proxy->options & PR_O_DISABLE404) &&
1678 (check->server->next_state != SRV_ST_STOPPED) &&
1679 (check->code == 404)) {
1680 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001681 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001682 goto out;
1683 }
1684
1685 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1686 /* Make GCC happy ; initialize match to a failure state. */
1687 match = inverse;
1688 status = expect->err_status;
1689
1690 switch (expect->type) {
1691 case TCPCHK_EXPECT_HTTP_STATUS:
1692 match = 0;
1693 for (i = 0; i < expect->codes.num; i++) {
1694 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1695 sl->info.res.status <= expect->codes.codes[i][1]) {
1696 match = 1;
1697 break;
1698 }
1699 }
1700
1701 /* Set status and description in case of error */
1702 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1703 if (LIST_ISEMPTY(&expect->onerror_fmt))
1704 desc = htx_sl_res_reason(sl);
1705 break;
1706 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1707 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1708
1709 /* Set status and description in case of error */
1710 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1711 if (LIST_ISEMPTY(&expect->onerror_fmt))
1712 desc = htx_sl_res_reason(sl);
1713 break;
1714
1715 case TCPCHK_EXPECT_HTTP_HEADER: {
1716 struct http_hdr_ctx ctx;
1717 struct ist npat, vpat, value;
1718 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1719
1720 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1721 nbuf = alloc_trash_chunk();
1722 if (!nbuf) {
1723 status = HCHK_STATUS_L7RSP;
1724 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001725 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001726 goto error;
1727 }
1728 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1729 if (!b_data(nbuf)) {
1730 status = HCHK_STATUS_L7RSP;
1731 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001732 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001733 goto error;
1734 }
1735 npat = ist2(b_orig(nbuf), b_data(nbuf));
1736 }
1737 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1738 npat = expect->hdr.name;
1739
1740 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1741 vbuf = alloc_trash_chunk();
1742 if (!vbuf) {
1743 status = HCHK_STATUS_L7RSP;
1744 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001745 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001746 goto error;
1747 }
1748 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1749 if (!b_data(vbuf)) {
1750 status = HCHK_STATUS_L7RSP;
1751 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001752 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001753 goto error;
1754 }
1755 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1756 }
1757 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1758 vpat = expect->hdr.value;
1759
1760 match = 0;
1761 ctx.blk = NULL;
1762 while (1) {
1763 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1764 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1765 if (!http_find_str_header(htx, npat, &ctx, full))
1766 goto end_of_match;
1767 break;
1768 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1769 if (!http_find_pfx_header(htx, npat, &ctx, full))
1770 goto end_of_match;
1771 break;
1772 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1773 if (!http_find_sfx_header(htx, npat, &ctx, full))
1774 goto end_of_match;
1775 break;
1776 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1777 if (!http_find_sub_header(htx, npat, &ctx, full))
1778 goto end_of_match;
1779 break;
1780 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1781 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1782 goto end_of_match;
1783 break;
1784 default:
1785 /* should never happen */
1786 goto end_of_match;
1787 }
1788
1789 /* A header has matched the name pattern, let's test its
1790 * value now (always defined from there). If there is no
1791 * value pattern, it is a good match.
1792 */
1793
1794 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1795 match = 1;
1796 goto end_of_match;
1797 }
1798
1799 value = ctx.value;
1800 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1801 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1802 if (isteq(value, vpat)) {
1803 match = 1;
1804 goto end_of_match;
1805 }
1806 break;
1807 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1808 if (istlen(value) < istlen(vpat))
1809 break;
1810 value = ist2(istptr(value), istlen(vpat));
1811 if (isteq(value, vpat)) {
1812 match = 1;
1813 goto end_of_match;
1814 }
1815 break;
1816 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1817 if (istlen(value) < istlen(vpat))
1818 break;
1819 value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
1820 if (isteq(value, vpat)) {
1821 match = 1;
1822 goto end_of_match;
1823 }
1824 break;
1825 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1826 if (isttest(istist(value, vpat))) {
1827 match = 1;
1828 goto end_of_match;
1829 }
1830 break;
1831 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1832 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1833 match = 1;
1834 goto end_of_match;
1835 }
1836 break;
1837 }
1838 }
1839
1840 end_of_match:
1841 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1842 if (LIST_ISEMPTY(&expect->onerror_fmt))
1843 desc = htx_sl_res_reason(sl);
1844 break;
1845 }
1846
1847 case TCPCHK_EXPECT_HTTP_BODY:
1848 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1849 case TCPCHK_EXPECT_HTTP_BODY_LF:
1850 match = 0;
1851 chunk_reset(&trash);
1852 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1853 enum htx_blk_type type = htx_get_blk_type(blk);
1854
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001855 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001856 break;
1857 if (type == HTX_BLK_DATA) {
1858 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1859 break;
1860 }
1861 }
1862
1863 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001864 if (!last_read) {
1865 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001866 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001867 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001868 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1869 if (LIST_ISEMPTY(&expect->onerror_fmt))
1870 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001871 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001872 goto error;
1873 }
1874
1875 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1876 tmp = alloc_trash_chunk();
1877 if (!tmp) {
1878 status = HCHK_STATUS_L7RSP;
1879 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001880 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001881 goto error;
1882 }
1883 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1884 if (!b_data(tmp)) {
1885 status = HCHK_STATUS_L7RSP;
1886 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001887 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001888 goto error;
1889 }
1890 }
1891
1892 if (!last_read &&
1893 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1894 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1895 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1896 ret = TCPCHK_EVAL_WAIT;
1897 goto out;
1898 }
1899
1900 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1901 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1902 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1903 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1904 else
1905 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1906
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001907 /* Wait for more data on mismatch only if no minimum is defined (-1),
1908 * otherwise the absence of match is already conclusive.
1909 */
1910 if (!match && !last_read && (expect->min_recv == -1)) {
1911 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001912 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001913 goto out;
1914 }
1915
Willy Tarreau51cd5952020-06-05 12:25:38 +02001916 /* Set status and description in case of error */
1917 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1918 if (LIST_ISEMPTY(&expect->onerror_fmt))
1919 desc = (inverse
1920 ? ist("HTTP check matched unwanted content")
1921 : ist("HTTP content check did not match"));
1922 break;
1923
1924
1925 default:
1926 /* should never happen */
1927 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1928 goto error;
1929 }
1930
Christopher Faulet147b8c92021-04-10 09:00:38 +02001931 if (!(match ^ inverse)) {
1932 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001933 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001934 }
1935
1936 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001937
1938 out:
1939 free_trash_chunk(tmp);
1940 free_trash_chunk(nbuf);
1941 free_trash_chunk(vbuf);
1942 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001943 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001944 return ret;
1945
1946 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001947 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001948 ret = TCPCHK_EVAL_STOP;
1949 msg = alloc_trash_chunk();
1950 if (msg)
1951 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1952 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1953 goto out;
1954
1955 wait_more_data:
1956 ret = TCPCHK_EVAL_WAIT;
1957 goto out;
1958}
1959
1960/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1961 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1962 * if an error occurred.
1963 */
1964enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1965{
1966 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1967 struct tcpcheck_expect *expect = &rule->expect;
1968 struct buffer *msg = NULL, *tmp = NULL;
1969 struct ist desc = IST_NULL;
1970 enum healthcheck_status status;
1971 int match, inverse;
1972
Christopher Faulet147b8c92021-04-10 09:00:38 +02001973 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1974
Willy Tarreau51cd5952020-06-05 12:25:38 +02001975 last_read |= b_full(&check->bi);
1976
1977 /* The current expect might need more data than the previous one, check again
1978 * that the minimum amount data required to match is respected.
1979 */
1980 if (!last_read) {
1981 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1982 (b_data(&check->bi) < istlen(expect->data))) {
1983 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001984 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001985 goto out;
1986 }
1987 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1988 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001989 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001990 goto out;
1991 }
1992 }
1993
1994 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1995 /* Make GCC happy ; initialize match to a failure state. */
1996 match = inverse;
1997 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1998
1999 switch (expect->type) {
2000 case TCPCHK_EXPECT_STRING:
2001 case TCPCHK_EXPECT_BINARY:
2002 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2003 break;
2004 case TCPCHK_EXPECT_STRING_REGEX:
2005 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2006 break;
2007
2008 case TCPCHK_EXPECT_BINARY_REGEX:
2009 chunk_reset(&trash);
2010 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2011 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2012 break;
2013
2014 case TCPCHK_EXPECT_STRING_LF:
2015 case TCPCHK_EXPECT_BINARY_LF:
2016 match = 0;
2017 tmp = alloc_trash_chunk();
2018 if (!tmp) {
2019 status = HCHK_STATUS_L7RSP;
2020 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002021 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002022 goto error;
2023 }
2024 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2025 if (!b_data(tmp)) {
2026 status = HCHK_STATUS_L7RSP;
2027 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002028 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002029 goto error;
2030 }
2031 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2032 int len = tmp->data;
2033 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2034 status = HCHK_STATUS_L7RSP;
2035 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002036 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002037 goto error;
2038 }
2039 tmp->data = len;
2040 }
2041 if (b_data(&check->bi) < tmp->data) {
2042 if (!last_read) {
2043 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002044 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002045 goto out;
2046 }
2047 break;
2048 }
2049 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2050 break;
2051
2052 case TCPCHK_EXPECT_CUSTOM:
2053 if (expect->custom)
2054 ret = expect->custom(check, rule, last_read);
2055 goto out;
2056 default:
2057 /* Should never happen. */
2058 ret = TCPCHK_EVAL_STOP;
2059 goto out;
2060 }
2061
2062
2063 /* Wait for more data on mismatch only if no minimum is defined (-1),
2064 * otherwise the absence of match is already conclusive.
2065 */
2066 if (!match && !last_read && (expect->min_recv == -1)) {
2067 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002068 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002069 goto out;
2070 }
2071
2072 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002073 if (match ^ inverse) {
2074 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002075 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002076 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002077
2078 error:
2079 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002080 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002081 ret = TCPCHK_EVAL_STOP;
2082 msg = alloc_trash_chunk();
2083 if (msg)
2084 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2085 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2086 free_trash_chunk(msg);
2087
2088 out:
2089 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002090 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002091 return ret;
2092}
2093
2094/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2095 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2096 * waits.
2097 */
2098enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2099{
2100 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2101 struct act_rule *act_rule;
2102 enum act_return act_ret;
2103
2104 act_rule =rule->action_kw.rule;
2105 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2106 if (act_ret != ACT_RET_CONT) {
2107 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2108 tcpcheck_get_step_id(check, rule));
2109 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2110 ret = TCPCHK_EVAL_STOP;
2111 }
2112
2113 return ret;
2114}
2115
2116/* Executes a tcp-check ruleset. Note that this is called both from the
2117 * connection's wake() callback and from the check scheduling task. It returns
2118 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2119 * presenting the risk of an fd replacement.
2120 *
2121 * Please do NOT place any return statement in this function and only leave
2122 * via the out_end_tcpcheck label after setting retcode.
2123 */
2124int tcpcheck_main(struct check *check)
2125{
2126 struct tcpcheck_rule *rule;
2127 struct conn_stream *cs = check->cs;
2128 struct connection *conn = cs_conn(cs);
2129 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002130 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002131 enum tcpcheck_eval_ret eval_ret;
2132
2133 /* here, we know that the check is complete or that it failed */
2134 if (check->result != CHK_RES_UNKNOWN)
2135 goto out;
2136
Christopher Faulet147b8c92021-04-10 09:00:38 +02002137 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2138
Willy Tarreau51cd5952020-06-05 12:25:38 +02002139 /* Note: the conn-stream and the connection may only be undefined before
2140 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002141 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002142 */
2143
2144 /* 1- check for connection error, if any */
2145 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2146 goto out_end_tcpcheck;
2147
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002148 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002149 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002150 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002151 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002152 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2153 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002154
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002155 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002156 * tcp-check variables */
2157 else {
2158 struct tcpcheck_var *var;
2159
2160 /* First evaluation, create a session */
2161 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2162 if (!check->sess) {
2163 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002164 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002165 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2166 goto out_end_tcpcheck;
2167 }
2168 vars_init(&check->vars, SCOPE_CHECK);
2169 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2170
2171 /* Preset tcp-check variables */
2172 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2173 struct sample smp;
2174
2175 memset(&smp, 0, sizeof(smp));
2176 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2177 smp.data = var->data;
2178 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2179 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002180 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002181 }
2182
2183 /* Now evaluate the tcp-check rules */
2184
2185 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2186 check->code = 0;
2187 switch (rule->action) {
2188 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002189 /* Not the first connection, release it first */
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002190 if (cs && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002191 check->state |= CHK_ST_CLOSE_CONN;
2192 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002193 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002194
2195 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002196
2197 /* We are still waiting the connection gets closed */
2198 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002199 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002200 eval_ret = TCPCHK_EVAL_WAIT;
2201 break;
2202 }
2203
Christopher Faulet147b8c92021-04-10 09:00:38 +02002204 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002205 eval_ret = tcpcheck_eval_connect(check, rule);
2206
2207 /* Refresh conn-stream and connection */
2208 cs = check->cs;
2209 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002210 last_read = 0;
2211 must_read = ((cs && IS_HTX_CS(cs)) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002212 break;
2213 case TCPCHK_ACT_SEND:
2214 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002215 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002216 eval_ret = tcpcheck_eval_send(check, rule);
2217 must_read = 1;
2218 break;
2219 case TCPCHK_ACT_EXPECT:
2220 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002221 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002222 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002223 eval_ret = tcpcheck_eval_recv(check, rule);
2224 if (eval_ret == TCPCHK_EVAL_STOP)
2225 goto out_end_tcpcheck;
2226 else if (eval_ret == TCPCHK_EVAL_WAIT)
2227 goto out;
2228 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2229 must_read = 0;
2230 }
2231
2232 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2233 ? tcpcheck_eval_expect_http(check, rule, last_read)
2234 : tcpcheck_eval_expect(check, rule, last_read));
2235
2236 if (eval_ret == TCPCHK_EVAL_WAIT) {
2237 check->current_step = rule->expect.head;
2238 if (!(check->wait_list.events & SUB_RETRY_RECV))
2239 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2240 }
2241 break;
2242 case TCPCHK_ACT_ACTION_KW:
2243 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002244 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002245 eval_ret = tcpcheck_eval_action_kw(check, rule);
2246 break;
2247 default:
2248 /* Otherwise, just go to the next one and don't update
2249 * the current step
2250 */
2251 eval_ret = TCPCHK_EVAL_CONTINUE;
2252 break;
2253 }
2254
2255 switch (eval_ret) {
2256 case TCPCHK_EVAL_CONTINUE:
2257 break;
2258 case TCPCHK_EVAL_WAIT:
2259 goto out;
2260 case TCPCHK_EVAL_STOP:
2261 goto out_end_tcpcheck;
2262 }
2263 }
2264
2265 /* All rules was evaluated */
2266 if (check->current_step) {
2267 rule = check->current_step;
2268
Christopher Faulet147b8c92021-04-10 09:00:38 +02002269 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2270
Willy Tarreau51cd5952020-06-05 12:25:38 +02002271 if (rule->action == TCPCHK_ACT_EXPECT) {
2272 struct buffer *msg;
2273 enum healthcheck_status status;
2274
2275 if (check->server &&
2276 (check->server->proxy->options & PR_O_DISABLE404) &&
2277 (check->server->next_state != SRV_ST_STOPPED) &&
2278 (check->code == 404)) {
2279 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002280 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002281 goto out_end_tcpcheck;
2282 }
2283
2284 msg = alloc_trash_chunk();
2285 if (msg)
2286 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2287 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2288 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2289 free_trash_chunk(msg);
2290 }
2291 else if (rule->action == TCPCHK_ACT_CONNECT) {
2292 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2293 enum healthcheck_status status = HCHK_STATUS_L4OK;
2294#ifdef USE_OPENSSL
2295 if (ssl_sock_is_ssl(conn))
2296 status = HCHK_STATUS_L6OK;
2297#endif
2298 set_server_check_status(check, status, msg);
2299 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002300 else
2301 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002302 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002303 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002304 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002305 }
2306 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002307
2308 out_end_tcpcheck:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002309 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) {
2310 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002311 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002312 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002313
Christopher Fauletb381a502020-11-25 13:47:00 +01002314 /* the tcpcheck is finished, release in/out buffer now */
2315 check_release_buf(check, &check->bi);
2316 check_release_buf(check, &check->bo);
2317
Willy Tarreau51cd5952020-06-05 12:25:38 +02002318 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002319 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002320 return retcode;
2321}
2322
2323
2324/**************************************************************************/
2325/******************* Internals to parse tcp-check rules *******************/
2326/**************************************************************************/
2327struct action_kw_list tcp_check_keywords = {
2328 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2329};
2330
2331/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2332 * returned on error.
2333 */
2334struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2335 struct list *rules, struct action_kw *kw,
2336 const char *file, int line, char **errmsg)
2337{
2338 struct tcpcheck_rule *chk = NULL;
2339 struct act_rule *actrule = NULL;
2340
2341 actrule = calloc(1, sizeof(*actrule));
2342 if (!actrule) {
2343 memprintf(errmsg, "out of memory");
2344 goto error;
2345 }
2346 actrule->kw = kw;
2347 actrule->from = ACT_F_TCP_CHK;
2348
2349 cur_arg++;
2350 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2351 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2352 goto error;
2353 }
2354
2355 chk = calloc(1, sizeof(*chk));
2356 if (!chk) {
2357 memprintf(errmsg, "out of memory");
2358 goto error;
2359 }
2360 chk->action = TCPCHK_ACT_ACTION_KW;
2361 chk->action_kw.rule = actrule;
2362 return chk;
2363
2364 error:
2365 free(actrule);
2366 return NULL;
2367}
2368
2369/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2370 * returned on error.
2371 */
2372struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2373 const char *file, int line, char **errmsg)
2374{
2375 struct tcpcheck_rule *chk = NULL;
2376 struct sockaddr_storage *sk = NULL;
2377 char *comment = NULL, *sni = NULL, *alpn = NULL;
2378 struct sample_expr *port_expr = NULL;
2379 const struct mux_proto_list *mux_proto = NULL;
2380 unsigned short conn_opts = 0;
2381 long port = 0;
2382 int alpn_len = 0;
2383
2384 list_for_each_entry(chk, rules, list) {
2385 if (chk->action == TCPCHK_ACT_CONNECT)
2386 break;
2387 if (chk->action == TCPCHK_ACT_COMMENT ||
2388 chk->action == TCPCHK_ACT_ACTION_KW ||
2389 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2390 continue;
2391
2392 memprintf(errmsg, "first step MUST also be a 'connect', "
2393 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2394 "when there is a 'connect' step in the tcp-check ruleset");
2395 goto error;
2396 }
2397
2398 cur_arg++;
2399 while (*(args[cur_arg])) {
2400 if (strcmp(args[cur_arg], "default") == 0)
2401 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2402 else if (strcmp(args[cur_arg], "addr") == 0) {
2403 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002404
2405 if (!*(args[cur_arg+1])) {
2406 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2407 goto error;
2408 }
2409
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002410 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2411 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002412 if (!sk) {
2413 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2414 goto error;
2415 }
2416
Willy Tarreau51cd5952020-06-05 12:25:38 +02002417 cur_arg++;
2418 }
2419 else if (strcmp(args[cur_arg], "port") == 0) {
2420 const char *p, *end;
2421
2422 if (!*(args[cur_arg+1])) {
2423 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2424 goto error;
2425 }
2426 cur_arg++;
2427
2428 port = 0;
2429 release_sample_expr(port_expr);
2430 p = args[cur_arg]; end = p + strlen(p);
2431 port = read_uint(&p, end);
2432 if (p != end) {
2433 int idx = 0;
2434
2435 px->conf.args.ctx = ARGC_SRV;
2436 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet8551c342021-09-30 16:22:51 +02002437 file, line, errmsg, (px->cap & PR_CAP_DEF) ? NULL: &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002438
2439 if (!port_expr) {
2440 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2441 goto error;
2442 }
2443 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2444 memprintf(errmsg, "error detected while parsing port expression : "
2445 " fetch method '%s' extracts information from '%s', "
2446 "none of which is available here.\n",
2447 args[cur_arg], sample_src_names(port_expr->fetch->use));
2448 goto error;
2449 }
2450 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2451 }
2452 else if (port > 65535 || port < 1) {
2453 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2454 args[cur_arg]);
2455 goto error;
2456 }
2457 }
2458 else if (strcmp(args[cur_arg], "proto") == 0) {
2459 if (!*(args[cur_arg+1])) {
2460 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2461 goto error;
2462 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002463 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002464 if (!mux_proto) {
2465 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2466 goto error;
2467 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002468
2469 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2470 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2471 goto error;
2472 }
2473 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2474 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2475 goto error;
2476 }
2477
Willy Tarreau51cd5952020-06-05 12:25:38 +02002478 cur_arg++;
2479 }
2480 else if (strcmp(args[cur_arg], "comment") == 0) {
2481 if (!*(args[cur_arg+1])) {
2482 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2483 goto error;
2484 }
2485 cur_arg++;
2486 free(comment);
2487 comment = strdup(args[cur_arg]);
2488 if (!comment) {
2489 memprintf(errmsg, "out of memory");
2490 goto error;
2491 }
2492 }
2493 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2494 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2495 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2496 conn_opts |= TCPCHK_OPT_SOCKS4;
2497 else if (strcmp(args[cur_arg], "linger") == 0)
2498 conn_opts |= TCPCHK_OPT_LINGER;
2499#ifdef USE_OPENSSL
2500 else if (strcmp(args[cur_arg], "ssl") == 0) {
2501 px->options |= PR_O_TCPCHK_SSL;
2502 conn_opts |= TCPCHK_OPT_SSL;
2503 }
2504 else if (strcmp(args[cur_arg], "sni") == 0) {
2505 if (!*(args[cur_arg+1])) {
2506 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2507 goto error;
2508 }
2509 cur_arg++;
2510 free(sni);
2511 sni = strdup(args[cur_arg]);
2512 if (!sni) {
2513 memprintf(errmsg, "out of memory");
2514 goto error;
2515 }
2516 }
2517 else if (strcmp(args[cur_arg], "alpn") == 0) {
2518#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2519 free(alpn);
2520 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2521 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2522 goto error;
2523 }
2524 cur_arg++;
2525#else
2526 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2527 goto error;
2528#endif
2529 }
2530#endif /* USE_OPENSSL */
2531
2532 else {
2533 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2534#ifdef USE_OPENSSL
2535 ", 'ssl', 'sni', 'alpn'"
2536#endif /* USE_OPENSSL */
2537 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2538 args[cur_arg]);
2539 goto error;
2540 }
2541 cur_arg++;
2542 }
2543
2544 chk = calloc(1, sizeof(*chk));
2545 if (!chk) {
2546 memprintf(errmsg, "out of memory");
2547 goto error;
2548 }
2549 chk->action = TCPCHK_ACT_CONNECT;
2550 chk->comment = comment;
2551 chk->connect.port = port;
2552 chk->connect.options = conn_opts;
2553 chk->connect.sni = sni;
2554 chk->connect.alpn = alpn;
2555 chk->connect.alpn_len= alpn_len;
2556 chk->connect.port_expr= port_expr;
2557 chk->connect.mux_proto= mux_proto;
2558 if (sk)
2559 chk->connect.addr = *sk;
2560 return chk;
2561
2562 error:
2563 free(alpn);
2564 free(sni);
2565 free(comment);
2566 release_sample_expr(port_expr);
2567 return NULL;
2568}
2569
2570/* Parses and creates a tcp-check send rule. NULL is returned on error */
2571struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2572 const char *file, int line, char **errmsg)
2573{
2574 struct tcpcheck_rule *chk = NULL;
2575 char *comment = NULL, *data = NULL;
2576 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2577
2578 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2579 type = TCPCHK_SEND_BINARY_LF;
2580 else if (strcmp(args[cur_arg], "send-binary") == 0)
2581 type = TCPCHK_SEND_BINARY;
2582 else if (strcmp(args[cur_arg], "send-lf") == 0)
2583 type = TCPCHK_SEND_STRING_LF;
2584 else if (strcmp(args[cur_arg], "send") == 0)
2585 type = TCPCHK_SEND_STRING;
2586
2587 if (!*(args[cur_arg+1])) {
2588 memprintf(errmsg, "'%s' expects a %s as argument",
2589 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2590 goto error;
2591 }
2592
2593 data = args[cur_arg+1];
2594
2595 cur_arg += 2;
2596 while (*(args[cur_arg])) {
2597 if (strcmp(args[cur_arg], "comment") == 0) {
2598 if (!*(args[cur_arg+1])) {
2599 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2600 goto error;
2601 }
2602 cur_arg++;
2603 free(comment);
2604 comment = strdup(args[cur_arg]);
2605 if (!comment) {
2606 memprintf(errmsg, "out of memory");
2607 goto error;
2608 }
2609 }
2610 else {
2611 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2612 args[cur_arg]);
2613 goto error;
2614 }
2615 cur_arg++;
2616 }
2617
2618 chk = calloc(1, sizeof(*chk));
2619 if (!chk) {
2620 memprintf(errmsg, "out of memory");
2621 goto error;
2622 }
2623 chk->action = TCPCHK_ACT_SEND;
2624 chk->comment = comment;
2625 chk->send.type = type;
2626
2627 switch (chk->send.type) {
2628 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002629 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002630 if (!isttest(chk->send.data)) {
2631 memprintf(errmsg, "out of memory");
2632 goto error;
2633 }
2634 break;
2635 case TCPCHK_SEND_BINARY: {
2636 int len = chk->send.data.len;
2637 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2638 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2639 goto error;
2640 }
2641 chk->send.data.len = len;
2642 break;
2643 }
2644 case TCPCHK_SEND_STRING_LF:
2645 case TCPCHK_SEND_BINARY_LF:
2646 LIST_INIT(&chk->send.fmt);
2647 px->conf.args.ctx = ARGC_SRV;
2648 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2649 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2650 goto error;
2651 }
2652 break;
2653 case TCPCHK_SEND_HTTP:
2654 case TCPCHK_SEND_UNDEF:
2655 goto error;
2656 }
2657
2658 return chk;
2659
2660 error:
2661 free(chk);
2662 free(comment);
2663 return NULL;
2664}
2665
2666/* Parses and creates a http-check send rule. NULL is returned on error */
2667struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2668 const char *file, int line, char **errmsg)
2669{
2670 struct tcpcheck_rule *chk = NULL;
2671 struct tcpcheck_http_hdr *hdr = NULL;
2672 struct http_hdr hdrs[global.tune.max_http_hdr];
2673 char *meth = NULL, *uri = NULL, *vsn = NULL;
2674 char *body = NULL, *comment = NULL;
2675 unsigned int flags = 0;
2676 int i = 0, host_hdr = -1;
2677
2678 cur_arg++;
2679 while (*(args[cur_arg])) {
2680 if (strcmp(args[cur_arg], "meth") == 0) {
2681 if (!*(args[cur_arg+1])) {
2682 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2683 goto error;
2684 }
2685 cur_arg++;
2686 meth = args[cur_arg];
2687 }
2688 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2689 if (!*(args[cur_arg+1])) {
2690 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2691 goto error;
2692 }
2693 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2694 if (strcmp(args[cur_arg], "uri-lf") == 0)
2695 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2696 cur_arg++;
2697 uri = args[cur_arg];
2698 }
2699 else if (strcmp(args[cur_arg], "ver") == 0) {
2700 if (!*(args[cur_arg+1])) {
2701 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2702 goto error;
2703 }
2704 cur_arg++;
2705 vsn = args[cur_arg];
2706 }
2707 else if (strcmp(args[cur_arg], "hdr") == 0) {
2708 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2709 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2710 goto error;
2711 }
2712
2713 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2714 if (host_hdr >= 0) {
2715 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2716 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2717 goto error;
2718 }
2719 host_hdr = i;
2720 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002721 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002722 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2723 goto skip_hdr;
2724
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002725 hdrs[i].n = ist(args[cur_arg + 1]);
2726 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002727 i++;
2728 skip_hdr:
2729 cur_arg += 2;
2730 }
2731 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2732 if (!*(args[cur_arg+1])) {
2733 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2734 goto error;
2735 }
2736 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2737 if (strcmp(args[cur_arg], "body-lf") == 0)
2738 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2739 cur_arg++;
2740 body = args[cur_arg];
2741 }
2742 else if (strcmp(args[cur_arg], "comment") == 0) {
2743 if (!*(args[cur_arg+1])) {
2744 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2745 goto error;
2746 }
2747 cur_arg++;
2748 free(comment);
2749 comment = strdup(args[cur_arg]);
2750 if (!comment) {
2751 memprintf(errmsg, "out of memory");
2752 goto error;
2753 }
2754 }
2755 else {
2756 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2757 " but got '%s' as argument.", args[cur_arg]);
2758 goto error;
2759 }
2760 cur_arg++;
2761 }
2762
2763 hdrs[i].n = hdrs[i].v = IST_NULL;
2764
2765 chk = calloc(1, sizeof(*chk));
2766 if (!chk) {
2767 memprintf(errmsg, "out of memory");
2768 goto error;
2769 }
2770 chk->action = TCPCHK_ACT_SEND;
2771 chk->comment = comment; comment = NULL;
2772 chk->send.type = TCPCHK_SEND_HTTP;
2773 chk->send.http.flags = flags;
2774 LIST_INIT(&chk->send.http.hdrs);
2775
2776 if (meth) {
2777 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2778 chk->send.http.meth.str.area = strdup(meth);
2779 chk->send.http.meth.str.data = strlen(meth);
2780 if (!chk->send.http.meth.str.area) {
2781 memprintf(errmsg, "out of memory");
2782 goto error;
2783 }
2784 }
2785 if (uri) {
2786 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2787 LIST_INIT(&chk->send.http.uri_fmt);
2788 px->conf.args.ctx = ARGC_SRV;
2789 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2790 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2791 goto error;
2792 }
2793 }
2794 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002795 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002796 if (!isttest(chk->send.http.uri)) {
2797 memprintf(errmsg, "out of memory");
2798 goto error;
2799 }
2800 }
2801 }
2802 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002803 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002804 if (!isttest(chk->send.http.vsn)) {
2805 memprintf(errmsg, "out of memory");
2806 goto error;
2807 }
2808 }
2809 for (i = 0; istlen(hdrs[i].n); i++) {
2810 hdr = calloc(1, sizeof(*hdr));
2811 if (!hdr) {
2812 memprintf(errmsg, "out of memory");
2813 goto error;
2814 }
2815 LIST_INIT(&hdr->value);
2816 hdr->name = istdup(hdrs[i].n);
2817 if (!isttest(hdr->name)) {
2818 memprintf(errmsg, "out of memory");
2819 goto error;
2820 }
2821
2822 ist0(hdrs[i].v);
2823 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2824 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002825 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002826 hdr = NULL;
2827 }
2828
2829 if (body) {
2830 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2831 LIST_INIT(&chk->send.http.body_fmt);
2832 px->conf.args.ctx = ARGC_SRV;
2833 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2834 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2835 goto error;
2836 }
2837 }
2838 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002839 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002840 if (!isttest(chk->send.http.body)) {
2841 memprintf(errmsg, "out of memory");
2842 goto error;
2843 }
2844 }
2845 }
2846
2847 return chk;
2848
2849 error:
2850 free_tcpcheck_http_hdr(hdr);
2851 free_tcpcheck(chk, 0);
2852 free(comment);
2853 return NULL;
2854}
2855
2856/* Parses and creates a http-check comment rule. NULL is returned on error */
2857struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2858 const char *file, int line, char **errmsg)
2859{
2860 struct tcpcheck_rule *chk = NULL;
2861 char *comment = NULL;
2862
2863 if (!*(args[cur_arg+1])) {
2864 memprintf(errmsg, "expects a string as argument");
2865 goto error;
2866 }
2867 cur_arg++;
2868 comment = strdup(args[cur_arg]);
2869 if (!comment) {
2870 memprintf(errmsg, "out of memory");
2871 goto error;
2872 }
2873
2874 chk = calloc(1, sizeof(*chk));
2875 if (!chk) {
2876 memprintf(errmsg, "out of memory");
2877 goto error;
2878 }
2879 chk->action = TCPCHK_ACT_COMMENT;
2880 chk->comment = comment;
2881 return chk;
2882
2883 error:
2884 free(comment);
2885 return NULL;
2886}
2887
2888/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2889 * on error. <proto> is set to the right protocol flags (covered by the
2890 * TCPCHK_RULES_PROTO_CHK mask).
2891 */
2892struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2893 struct list *rules, unsigned int proto,
2894 const char *file, int line, char **errmsg)
2895{
2896 struct tcpcheck_rule *prev_check, *chk = NULL;
2897 struct sample_expr *status_expr = NULL;
2898 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2899 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2900 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2901 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2902 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2903 unsigned int flags = 0;
2904 long min_recv = -1;
2905 int inverse = 0;
2906
2907 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2908 if (!*(args[cur_arg+1])) {
2909 memprintf(errmsg, "expects at least a matching pattern as arguments");
2910 goto error;
2911 }
2912
2913 cur_arg++;
2914 while (*(args[cur_arg])) {
2915 int in_pattern = 0;
2916
2917 rescan:
2918 if (strcmp(args[cur_arg], "min-recv") == 0) {
2919 if (in_pattern) {
2920 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2921 goto error;
2922 }
2923 if (!*(args[cur_arg+1])) {
2924 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2925 goto error;
2926 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002927 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002928 cur_arg++;
2929 min_recv = atol(args[cur_arg]);
2930 if (min_recv < -1 || min_recv > INT_MAX) {
2931 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2932 goto error;
2933 }
2934 }
2935 else if (*(args[cur_arg]) == '!') {
2936 in_pattern = 1;
2937 while (*(args[cur_arg]) == '!') {
2938 inverse = !inverse;
2939 args[cur_arg]++;
2940 }
2941 if (!*(args[cur_arg]))
2942 cur_arg++;
2943 goto rescan;
2944 }
2945 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2946 if (type != TCPCHK_EXPECT_UNDEF) {
2947 memprintf(errmsg, "only on pattern expected");
2948 goto error;
2949 }
2950 if (proto != TCPCHK_RULES_HTTP_CHK)
2951 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2952 else
2953 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2954
2955 if (!*(args[cur_arg+1])) {
2956 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2957 goto error;
2958 }
2959 cur_arg++;
2960 pattern = args[cur_arg];
2961 }
2962 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2963 if (proto == TCPCHK_RULES_HTTP_CHK)
2964 goto bad_http_kw;
2965 if (type != TCPCHK_EXPECT_UNDEF) {
2966 memprintf(errmsg, "only on pattern expected");
2967 goto error;
2968 }
2969 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2970
2971 if (!*(args[cur_arg+1])) {
2972 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2973 goto error;
2974 }
2975 cur_arg++;
2976 pattern = args[cur_arg];
2977 }
2978 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2979 if (type != TCPCHK_EXPECT_UNDEF) {
2980 memprintf(errmsg, "only on pattern expected");
2981 goto error;
2982 }
2983 if (proto != TCPCHK_RULES_HTTP_CHK)
2984 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2985 else {
2986 if (*(args[cur_arg]) != 's')
2987 goto bad_http_kw;
2988 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2989 }
2990
2991 if (!*(args[cur_arg+1])) {
2992 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2993 goto error;
2994 }
2995 cur_arg++;
2996 pattern = args[cur_arg];
2997 }
2998 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
2999 if (proto != TCPCHK_RULES_HTTP_CHK)
3000 goto bad_tcp_kw;
3001 if (type != TCPCHK_EXPECT_UNDEF) {
3002 memprintf(errmsg, "only on pattern expected");
3003 goto error;
3004 }
3005 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3006
3007 if (!*(args[cur_arg+1])) {
3008 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3009 goto error;
3010 }
3011 cur_arg++;
3012 pattern = args[cur_arg];
3013 }
3014 else if (strcmp(args[cur_arg], "custom") == 0) {
3015 if (in_pattern) {
3016 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3017 goto error;
3018 }
3019 if (type != TCPCHK_EXPECT_UNDEF) {
3020 memprintf(errmsg, "only on pattern expected");
3021 goto error;
3022 }
3023 type = TCPCHK_EXPECT_CUSTOM;
3024 }
3025 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3026 int orig_arg = cur_arg;
3027
3028 if (proto != TCPCHK_RULES_HTTP_CHK)
3029 goto bad_tcp_kw;
3030 if (type != TCPCHK_EXPECT_UNDEF) {
3031 memprintf(errmsg, "only on pattern expected");
3032 goto error;
3033 }
3034 type = TCPCHK_EXPECT_HTTP_HEADER;
3035
3036 if (strcmp(args[cur_arg], "fhdr") == 0)
3037 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3038
3039 /* Parse the name pattern, mandatory */
3040 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3041 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3042 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3043 args[orig_arg]);
3044 goto error;
3045 }
3046
3047 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3048 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3049
3050 cur_arg += 2;
3051 if (strcmp(args[cur_arg], "-m") == 0) {
3052 if (!*(args[cur_arg+1])) {
3053 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3054 args[orig_arg], args[cur_arg]);
3055 goto error;
3056 }
3057 if (strcmp(args[cur_arg+1], "str") == 0)
3058 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3059 else if (strcmp(args[cur_arg+1], "beg") == 0)
3060 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3061 else if (strcmp(args[cur_arg+1], "end") == 0)
3062 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3063 else if (strcmp(args[cur_arg+1], "sub") == 0)
3064 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3065 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3066 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3067 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3068 args[orig_arg]);
3069 goto error;
3070 }
3071 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3072 }
3073 else {
3074 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3075 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3076 goto error;
3077 }
3078 cur_arg += 2;
3079 }
3080 else
3081 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3082 npat = args[cur_arg];
3083
3084 if (!*(args[cur_arg+1]) ||
3085 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3086 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3087 goto next;
3088 }
3089 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3090 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3091
3092 /* Parse the value pattern, optional */
3093 if (strcmp(args[cur_arg+2], "-m") == 0) {
3094 cur_arg += 2;
3095 if (!*(args[cur_arg+1])) {
3096 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3097 args[orig_arg], args[cur_arg]);
3098 goto error;
3099 }
3100 if (strcmp(args[cur_arg+1], "str") == 0)
3101 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3102 else if (strcmp(args[cur_arg+1], "beg") == 0)
3103 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3104 else if (strcmp(args[cur_arg+1], "end") == 0)
3105 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3106 else if (strcmp(args[cur_arg+1], "sub") == 0)
3107 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3108 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3109 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3110 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3111 args[orig_arg]);
3112 goto error;
3113 }
3114 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3115 }
3116 else {
3117 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3118 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3119 goto error;
3120 }
3121 }
3122 else
3123 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3124
3125 if (!*(args[cur_arg+2])) {
3126 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3127 goto error;
3128 }
3129 vpat = args[cur_arg+2];
3130 cur_arg += 2;
3131 }
3132 else if (strcmp(args[cur_arg], "comment") == 0) {
3133 if (in_pattern) {
3134 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3135 goto error;
3136 }
3137 if (!*(args[cur_arg+1])) {
3138 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3139 goto error;
3140 }
3141 cur_arg++;
3142 free(comment);
3143 comment = strdup(args[cur_arg]);
3144 if (!comment) {
3145 memprintf(errmsg, "out of memory");
3146 goto error;
3147 }
3148 }
3149 else if (strcmp(args[cur_arg], "on-success") == 0) {
3150 if (in_pattern) {
3151 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3152 goto error;
3153 }
3154 if (!*(args[cur_arg+1])) {
3155 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3156 goto error;
3157 }
3158 cur_arg++;
3159 on_success_msg = args[cur_arg];
3160 }
3161 else if (strcmp(args[cur_arg], "on-error") == 0) {
3162 if (in_pattern) {
3163 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3164 goto error;
3165 }
3166 if (!*(args[cur_arg+1])) {
3167 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3168 goto error;
3169 }
3170 cur_arg++;
3171 on_error_msg = args[cur_arg];
3172 }
3173 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3174 if (in_pattern) {
3175 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3176 goto error;
3177 }
3178 if (!*(args[cur_arg+1])) {
3179 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3180 goto error;
3181 }
3182 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3183 ok_st = HCHK_STATUS_L7OKD;
3184 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3185 ok_st = HCHK_STATUS_L7OKCD;
3186 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3187 ok_st = HCHK_STATUS_L6OK;
3188 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3189 ok_st = HCHK_STATUS_L4OK;
3190 else {
3191 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3192 args[cur_arg], args[cur_arg+1]);
3193 goto error;
3194 }
3195 cur_arg++;
3196 }
3197 else if (strcmp(args[cur_arg], "error-status") == 0) {
3198 if (in_pattern) {
3199 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3200 goto error;
3201 }
3202 if (!*(args[cur_arg+1])) {
3203 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3204 goto error;
3205 }
3206 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3207 err_st = HCHK_STATUS_L7RSP;
3208 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3209 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003210 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3211 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003212 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3213 err_st = HCHK_STATUS_L6RSP;
3214 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3215 err_st = HCHK_STATUS_L4CON;
3216 else {
3217 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3218 args[cur_arg], args[cur_arg+1]);
3219 goto error;
3220 }
3221 cur_arg++;
3222 }
3223 else if (strcmp(args[cur_arg], "status-code") == 0) {
3224 int idx = 0;
3225
3226 if (in_pattern) {
3227 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3228 goto error;
3229 }
3230 if (!*(args[cur_arg+1])) {
3231 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3232 goto error;
3233 }
3234
3235 cur_arg++;
3236 release_sample_expr(status_expr);
3237 px->conf.args.ctx = ARGC_SRV;
3238 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet8551c342021-09-30 16:22:51 +02003239 file, line, errmsg, (px->cap & PR_CAP_DEF) ? NULL: &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003240 if (!status_expr) {
3241 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3242 goto error;
3243 }
3244 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3245 memprintf(errmsg, "error detected while parsing status-code expression : "
3246 " fetch method '%s' extracts information from '%s', "
3247 "none of which is available here.\n",
3248 args[cur_arg], sample_src_names(status_expr->fetch->use));
3249 goto error;
3250 }
3251 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3252 }
3253 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3254 if (in_pattern) {
3255 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3256 goto error;
3257 }
3258 if (!*(args[cur_arg+1])) {
3259 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3260 goto error;
3261 }
3262 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3263 tout_st = HCHK_STATUS_L7TOUT;
3264 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3265 tout_st = HCHK_STATUS_L6TOUT;
3266 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3267 tout_st = HCHK_STATUS_L4TOUT;
3268 else {
3269 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3270 args[cur_arg], args[cur_arg+1]);
3271 goto error;
3272 }
3273 cur_arg++;
3274 }
3275 else {
3276 if (proto == TCPCHK_RULES_HTTP_CHK) {
3277 bad_http_kw:
3278 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3279 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3280 }
3281 else {
3282 bad_tcp_kw:
3283 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3284 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3285 }
3286 goto error;
3287 }
3288 next:
3289 cur_arg++;
3290 }
3291
3292 chk = calloc(1, sizeof(*chk));
3293 if (!chk) {
3294 memprintf(errmsg, "out of memory");
3295 goto error;
3296 }
3297 chk->action = TCPCHK_ACT_EXPECT;
3298 LIST_INIT(&chk->expect.onerror_fmt);
3299 LIST_INIT(&chk->expect.onsuccess_fmt);
3300 chk->comment = comment; comment = NULL;
3301 chk->expect.type = type;
3302 chk->expect.min_recv = min_recv;
3303 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3304 chk->expect.ok_status = ok_st;
3305 chk->expect.err_status = err_st;
3306 chk->expect.tout_status = tout_st;
3307 chk->expect.status_expr = status_expr; status_expr = NULL;
3308
3309 if (on_success_msg) {
3310 px->conf.args.ctx = ARGC_SRV;
3311 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3312 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3313 goto error;
3314 }
3315 }
3316 if (on_error_msg) {
3317 px->conf.args.ctx = ARGC_SRV;
3318 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3319 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3320 goto error;
3321 }
3322 }
3323
3324 switch (chk->expect.type) {
3325 case TCPCHK_EXPECT_HTTP_STATUS: {
3326 const char *p = pattern;
3327 unsigned int c1,c2;
3328
3329 chk->expect.codes.codes = NULL;
3330 chk->expect.codes.num = 0;
3331 while (1) {
3332 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3333 if (*p == '-') {
3334 p++;
3335 c2 = read_uint(&p, pattern + strlen(pattern));
3336 }
3337 if (c1 > c2) {
3338 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3339 goto error;
3340 }
3341
3342 chk->expect.codes.num++;
3343 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3344 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3345 if (!chk->expect.codes.codes) {
3346 memprintf(errmsg, "out of memory");
3347 goto error;
3348 }
3349 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3350 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3351
3352 if (*p == '\0')
3353 break;
3354 if (*p != ',') {
3355 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3356 goto error;
3357 }
3358 p++;
3359 }
3360 break;
3361 }
3362 case TCPCHK_EXPECT_STRING:
3363 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003364 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003365 if (!isttest(chk->expect.data)) {
3366 memprintf(errmsg, "out of memory");
3367 goto error;
3368 }
3369 break;
3370 case TCPCHK_EXPECT_BINARY: {
3371 int len = chk->expect.data.len;
3372
3373 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3374 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3375 goto error;
3376 }
3377 chk->expect.data.len = len;
3378 break;
3379 }
3380 case TCPCHK_EXPECT_STRING_REGEX:
3381 case TCPCHK_EXPECT_BINARY_REGEX:
3382 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3383 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3384 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3385 if (!chk->expect.regex)
3386 goto error;
3387 break;
3388
3389 case TCPCHK_EXPECT_STRING_LF:
3390 case TCPCHK_EXPECT_BINARY_LF:
3391 case TCPCHK_EXPECT_HTTP_BODY_LF:
3392 LIST_INIT(&chk->expect.fmt);
3393 px->conf.args.ctx = ARGC_SRV;
3394 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3395 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3396 goto error;
3397 }
3398 break;
3399
3400 case TCPCHK_EXPECT_HTTP_HEADER:
3401 if (!npat) {
3402 memprintf(errmsg, "unexpected error, undefined header name pattern");
3403 goto error;
3404 }
3405 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3406 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3407 if (!chk->expect.hdr.name_re)
3408 goto error;
3409 }
3410 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3411 px->conf.args.ctx = ARGC_SRV;
3412 LIST_INIT(&chk->expect.hdr.name_fmt);
3413 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3414 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3415 goto error;
3416 }
3417 }
3418 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003419 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003420 if (!isttest(chk->expect.hdr.name)) {
3421 memprintf(errmsg, "out of memory");
3422 goto error;
3423 }
3424 }
3425
3426 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3427 chk->expect.hdr.value = IST_NULL;
3428 break;
3429 }
3430
3431 if (!vpat) {
3432 memprintf(errmsg, "unexpected error, undefined header value pattern");
3433 goto error;
3434 }
3435 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3436 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3437 if (!chk->expect.hdr.value_re)
3438 goto error;
3439 }
3440 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3441 px->conf.args.ctx = ARGC_SRV;
3442 LIST_INIT(&chk->expect.hdr.value_fmt);
3443 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3444 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3445 goto error;
3446 }
3447 }
3448 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003449 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003450 if (!isttest(chk->expect.hdr.value)) {
3451 memprintf(errmsg, "out of memory");
3452 goto error;
3453 }
3454 }
3455
3456 break;
3457 case TCPCHK_EXPECT_CUSTOM:
3458 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3459 break;
3460 case TCPCHK_EXPECT_UNDEF:
3461 memprintf(errmsg, "pattern not found");
3462 goto error;
3463 }
3464
3465 /* All tcp-check expect points back to the first inverse expect rule in
3466 * a chain of one or more expect rule, potentially itself.
3467 */
3468 chk->expect.head = chk;
3469 list_for_each_entry_rev(prev_check, rules, list) {
3470 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3471 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3472 chk->expect.head = prev_check;
3473 continue;
3474 }
3475 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3476 break;
3477 }
3478 return chk;
3479
3480 error:
3481 free_tcpcheck(chk, 0);
3482 free(comment);
3483 release_sample_expr(status_expr);
3484 return NULL;
3485}
3486
3487/* Overwrites fields of the old http send rule with those of the new one. When
3488 * replaced, old values are freed and replaced by the new ones. New values are
3489 * not copied but transferred. At the end <new> should be empty and can be
3490 * safely released. This function never fails.
3491 */
3492void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3493{
3494 struct logformat_node *lf, *lfb;
3495 struct tcpcheck_http_hdr *hdr, *bhdr;
3496
3497
3498 if (new->send.http.meth.str.area) {
3499 free(old->send.http.meth.str.area);
3500 old->send.http.meth.meth = new->send.http.meth.meth;
3501 old->send.http.meth.str.area = new->send.http.meth.str.area;
3502 old->send.http.meth.str.data = new->send.http.meth.str.data;
3503 new->send.http.meth.str = BUF_NULL;
3504 }
3505
3506 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3507 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3508 istfree(&old->send.http.uri);
3509 else
3510 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3511 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3512 old->send.http.uri = new->send.http.uri;
3513 new->send.http.uri = IST_NULL;
3514 }
3515 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3516 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3517 istfree(&old->send.http.uri);
3518 else
3519 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3520 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3521 LIST_INIT(&old->send.http.uri_fmt);
3522 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003523 LIST_DELETE(&lf->list);
3524 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003525 }
3526 }
3527
3528 if (isttest(new->send.http.vsn)) {
3529 istfree(&old->send.http.vsn);
3530 old->send.http.vsn = new->send.http.vsn;
3531 new->send.http.vsn = IST_NULL;
3532 }
3533
Christopher Faulet94cd9a42022-07-05 15:33:53 +02003534 if (!LIST_ISEMPTY(&new->send.http.hdrs)) {
3535 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3536 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
3537 LIST_DELETE(&hdr->list);
3538 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
3539 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02003540 }
3541
3542 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3543 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3544 istfree(&old->send.http.body);
3545 else
3546 free_tcpcheck_fmt(&old->send.http.body_fmt);
3547 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3548 old->send.http.body = new->send.http.body;
3549 new->send.http.body = IST_NULL;
3550 }
3551 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3552 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3553 istfree(&old->send.http.body);
3554 else
3555 free_tcpcheck_fmt(&old->send.http.body_fmt);
3556 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3557 LIST_INIT(&old->send.http.body_fmt);
3558 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003559 LIST_DELETE(&lf->list);
3560 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003561 }
3562 }
3563}
3564
3565/* Internal function used to add an http-check rule in a list during the config
3566 * parsing step. Depending on its type, and the previously inserted rules, a
3567 * specific action may be performed or an error may be reported. This functions
3568 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3569 * message.
3570 */
3571int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3572{
3573 struct tcpcheck_rule *r;
3574
3575 /* the implicit send rule coming from an "option httpchk" line must be
3576 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003577 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003578 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003579 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003580 * sure the ruleset remains valid.
3581 */
3582
3583 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3584 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3585 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3586 * following tests are performed :
3587 *
3588 * 1- If there is no such rule or if it is not a send rule, the implicit send
3589 * rule is pushed in front of the ruleset
3590 *
3591 * 2- If it is another implicit send rule, it is replaced with the new one.
3592 *
3593 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3594 * both, overwriting the old send rule (the explicit one) with info of the
3595 * new send rule (the implicit one).
3596 */
3597 r = get_first_tcpcheck_rule(rules);
3598 if (r && r->action == TCPCHK_ACT_CONNECT)
3599 r = get_next_tcpcheck_rule(rules, r);
3600 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003601 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003602 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003603 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003604 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003605 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003606 }
3607 else {
3608 tcpcheck_overwrite_send_http_rule(r, chk);
3609 free_tcpcheck(chk, 0);
3610 }
3611 }
3612 else {
3613 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3614 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3615 * with an existing implicit send rule, if any. At the end, if there is no error,
3616 * the rule is appended to the list.
3617 */
3618
3619 r = get_last_tcpcheck_rule(rules);
3620 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3621 /* no error */;
3622 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3623 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3624 chk->index+1);
3625 return 0;
3626 }
3627 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3628 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3629 chk->index+1);
3630 return 0;
3631 }
3632 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3633 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3634 chk->index+1);
3635 return 0;
3636 }
3637
3638 if (chk->action == TCPCHK_ACT_SEND) {
3639 r = get_first_tcpcheck_rule(rules);
3640 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3641 tcpcheck_overwrite_send_http_rule(r, chk);
3642 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003643 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003644 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3645 chk = r;
3646 }
3647 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003648 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003649 }
3650 return 1;
3651}
3652
3653/* Check tcp-check health-check configuration for the proxy <px>. */
3654static int check_proxy_tcpcheck(struct proxy *px)
3655{
3656 struct tcpcheck_rule *chk, *back;
3657 char *comment = NULL, *errmsg = NULL;
3658 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003659 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003660
3661 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3662 deinit_proxy_tcpcheck(px);
3663 goto out;
3664 }
3665
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003666 ha_free(&px->check_command);
3667 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003668
3669 if (!px->tcpcheck_rules.list) {
3670 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
3671 ret |= ERR_ALERT | ERR_FATAL;
3672 goto out;
3673 }
3674
3675 /* HTTP ruleset only : */
3676 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3677 struct tcpcheck_rule *next;
3678
3679 /* move remaining implicit send rule from "option httpchk" line to the right place.
3680 * If such rule exists, it must be the first one. In this case, the rule is moved
3681 * after the first connect rule, if any. Otherwise, nothing is done.
3682 */
3683 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3684 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3685 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3686 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003687 LIST_DELETE(&chk->list);
3688 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003689 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003690 }
3691 }
3692
3693 /* add implicit expect rule if the last one is a send. It is inherited from previous
3694 * versions where the http expect rule was optional. Now it is possible to chained
3695 * send/expect rules but the last expect may still be implicit.
3696 */
3697 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3698 if (chk && chk->action == TCPCHK_ACT_SEND) {
3699 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3700 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3701 px->conf.file, px->conf.line, &errmsg);
3702 if (!next) {
3703 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
3704 "(%s).\n", px->id, errmsg);
3705 free(errmsg);
3706 ret |= ERR_ALERT | ERR_FATAL;
3707 goto out;
3708 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003709 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003710 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003711 }
3712 }
3713
3714 /* For all ruleset: */
3715
3716 /* If there is no connect rule preceding all send / expect rules, an
3717 * implicit one is inserted before all others.
3718 */
3719 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3720 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3721 chk = calloc(1, sizeof(*chk));
3722 if (!chk) {
3723 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
3724 "(out of memory).\n", px->id);
3725 ret |= ERR_ALERT | ERR_FATAL;
3726 goto out;
3727 }
3728 chk->action = TCPCHK_ACT_CONNECT;
3729 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003730 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003731 }
3732
3733 /* Remove all comment rules. To do so, when a such rule is found, the
3734 * comment is assigned to the following rule(s).
3735 */
3736 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Faulet8af4ab82022-08-24 11:38:03 +02003737 struct tcpcheck_rule *next;
3738
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003739 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3740 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003741
3742 prev_action = chk->action;
3743 switch (chk->action) {
3744 case TCPCHK_ACT_COMMENT:
3745 free(comment);
3746 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003747 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003748 free(chk);
3749 break;
3750 case TCPCHK_ACT_CONNECT:
3751 if (!chk->comment && comment)
3752 chk->comment = strdup(comment);
Christopher Faulet8af4ab82022-08-24 11:38:03 +02003753 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3754 if (next && next->action == TCPCHK_ACT_SEND)
3755 chk->connect.options |= TCPCHK_OPT_HAS_DATA;
Tim Duesterhus588b3142020-05-29 14:35:51 +02003756 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003757 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003758 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003759 break;
3760 case TCPCHK_ACT_SEND:
3761 case TCPCHK_ACT_EXPECT:
3762 if (!chk->comment && comment)
3763 chk->comment = strdup(comment);
3764 break;
3765 }
3766 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003767 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003768
3769 out:
3770 return ret;
3771}
3772
3773void deinit_proxy_tcpcheck(struct proxy *px)
3774{
3775 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3776 px->tcpcheck_rules.flags = 0;
3777 px->tcpcheck_rules.list = NULL;
3778}
3779
3780static void deinit_tcpchecks()
3781{
3782 struct tcpcheck_ruleset *rs;
3783 struct tcpcheck_rule *r, *rb;
3784 struct ebpt_node *node, *next;
3785
3786 node = ebpt_first(&shared_tcpchecks);
3787 while (node) {
3788 next = ebpt_next(node);
3789 ebpt_delete(node);
3790 free(node->key);
3791 rs = container_of(node, typeof(*rs), node);
3792 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003793 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003794 free_tcpcheck(r, 0);
3795 }
3796 free(rs);
3797 node = next;
3798 }
3799}
3800
3801int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3802{
3803 struct tcpcheck_rule *tcpcheck, *prev_check;
3804 struct tcpcheck_expect *expect;
3805
Willy Tarreau6922e552021-03-22 21:11:45 +01003806 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003807 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003808 tcpcheck->action = TCPCHK_ACT_EXPECT;
3809
3810 expect = &tcpcheck->expect;
3811 expect->type = TCPCHK_EXPECT_STRING;
3812 LIST_INIT(&expect->onerror_fmt);
3813 LIST_INIT(&expect->onsuccess_fmt);
3814 expect->ok_status = HCHK_STATUS_L7OKD;
3815 expect->err_status = HCHK_STATUS_L7RSP;
3816 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003817 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003818 if (!isttest(expect->data)) {
3819 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3820 return 0;
3821 }
3822
3823 /* All tcp-check expect points back to the first inverse expect rule
3824 * in a chain of one or more expect rule, potentially itself.
3825 */
3826 tcpcheck->expect.head = tcpcheck;
3827 list_for_each_entry_rev(prev_check, rules->list, list) {
3828 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3829 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3830 tcpcheck->expect.head = prev_check;
3831 continue;
3832 }
3833 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3834 break;
3835 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003836 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003837 return 1;
3838}
3839
3840int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3841{
3842 struct tcpcheck_rule *tcpcheck;
3843 struct tcpcheck_send *send;
3844 const char *in;
3845 char *dst;
3846 int i;
3847
Willy Tarreau6922e552021-03-22 21:11:45 +01003848 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003849 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003850 tcpcheck->action = TCPCHK_ACT_SEND;
3851
3852 send = &tcpcheck->send;
3853 send->type = TCPCHK_SEND_STRING;
3854
3855 for (i = 0; strs[i]; i++)
3856 send->data.len += strlen(strs[i]);
3857
3858 send->data.ptr = malloc(istlen(send->data) + 1);
3859 if (!isttest(send->data)) {
3860 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3861 return 0;
3862 }
3863
3864 dst = istptr(send->data);
3865 for (i = 0; strs[i]; i++)
3866 for (in = strs[i]; (*dst = *in++); dst++);
3867 *dst = 0;
3868
Willy Tarreau2b718102021-04-21 07:32:39 +02003869 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003870 return 1;
3871}
3872
3873/* Parses the "tcp-check" proxy keyword */
3874static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003875 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003876 char **errmsg)
3877{
3878 struct tcpcheck_ruleset *rs = NULL;
3879 struct tcpcheck_rule *chk = NULL;
3880 int index, cur_arg, ret = 0;
3881
3882 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3883 ret = 1;
3884
3885 /* Deduce the ruleset name from the proxy info */
3886 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3887 ((curpx == defpx) ? "defaults" : curpx->id),
3888 curpx->conf.file, curpx->conf.line);
3889
3890 rs = find_tcpcheck_ruleset(b_orig(&trash));
3891 if (rs == NULL) {
3892 rs = create_tcpcheck_ruleset(b_orig(&trash));
3893 if (rs == NULL) {
3894 memprintf(errmsg, "out of memory.\n");
3895 goto error;
3896 }
3897 }
3898
3899 index = 0;
3900 if (!LIST_ISEMPTY(&rs->rules)) {
3901 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3902 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003903 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003904 }
3905
3906 cur_arg = 1;
3907 if (strcmp(args[cur_arg], "connect") == 0)
3908 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3909 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3910 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3911 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3912 else if (strcmp(args[cur_arg], "expect") == 0)
3913 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3914 else if (strcmp(args[cur_arg], "comment") == 0)
3915 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3916 else {
3917 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3918
3919 if (!kw) {
3920 action_kw_tcp_check_build_list(&trash);
3921 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3922 "%s%s. but got '%s'",
3923 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3924 goto error;
3925 }
3926 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3927 }
3928
3929 if (!chk) {
3930 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3931 goto error;
3932 }
3933 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3934
3935 /* No error: add the tcp-check rule in the list */
3936 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003937 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003938
3939 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3940 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3941 /* Use this ruleset if the proxy already has tcp-check enabled */
3942 curpx->tcpcheck_rules.list = &rs->rules;
3943 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3944 }
3945 else {
3946 /* mark this ruleset as unused for now */
3947 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3948 }
3949
3950 return ret;
3951
3952 error:
3953 free_tcpcheck(chk, 0);
3954 free_tcpcheck_ruleset(rs);
3955 return -1;
3956}
3957
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003958/* Parses the "http-check" proxy keyword */
3959static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003960 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003961 char **errmsg)
3962{
3963 struct tcpcheck_ruleset *rs = NULL;
3964 struct tcpcheck_rule *chk = NULL;
3965 int index, cur_arg, ret = 0;
3966
3967 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3968 ret = 1;
3969
3970 cur_arg = 1;
3971 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3972 /* enable a graceful server shutdown on an HTTP 404 response */
3973 curpx->options |= PR_O_DISABLE404;
3974 if (too_many_args(1, args, errmsg, NULL))
3975 goto error;
3976 goto out;
3977 }
3978 else if (strcmp(args[cur_arg], "send-state") == 0) {
3979 /* enable emission of the apparent state of a server in HTTP checks */
3980 curpx->options2 |= PR_O2_CHK_SNDST;
3981 if (too_many_args(1, args, errmsg, NULL))
3982 goto error;
3983 goto out;
3984 }
3985
3986 /* Deduce the ruleset name from the proxy info */
3987 chunk_printf(&trash, "*http-check-%s_%s-%d",
3988 ((curpx == defpx) ? "defaults" : curpx->id),
3989 curpx->conf.file, curpx->conf.line);
3990
3991 rs = find_tcpcheck_ruleset(b_orig(&trash));
3992 if (rs == NULL) {
3993 rs = create_tcpcheck_ruleset(b_orig(&trash));
3994 if (rs == NULL) {
3995 memprintf(errmsg, "out of memory.\n");
3996 goto error;
3997 }
3998 }
3999
4000 index = 0;
4001 if (!LIST_ISEMPTY(&rs->rules)) {
4002 chk = LIST_PREV(&rs->rules, typeof(chk), list);
4003 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4004 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004005 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004006 }
4007
4008 if (strcmp(args[cur_arg], "connect") == 0)
4009 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4010 else if (strcmp(args[cur_arg], "send") == 0)
4011 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4012 else if (strcmp(args[cur_arg], "expect") == 0)
4013 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4014 file, line, errmsg);
4015 else if (strcmp(args[cur_arg], "comment") == 0)
4016 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4017 else {
4018 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4019
4020 if (!kw) {
4021 action_kw_tcp_check_build_list(&trash);
4022 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4023 " 'send', 'expect'%s%s. but got '%s'",
4024 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4025 goto error;
4026 }
4027 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4028 }
4029
4030 if (!chk) {
4031 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4032 goto error;
4033 }
4034 ret = (*errmsg != NULL); /* Handle warning */
4035
4036 chk->index = index;
4037 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4038 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4039 /* Use this ruleset if the proxy already has http-check enabled */
4040 curpx->tcpcheck_rules.list = &rs->rules;
4041 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4042 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4043 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4044 curpx->tcpcheck_rules.list = NULL;
4045 goto error;
4046 }
4047 }
4048 else {
4049 /* mark this ruleset as unused for now */
4050 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004051 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004052 }
4053
4054 out:
4055 return ret;
4056
4057 error:
4058 free_tcpcheck(chk, 0);
4059 free_tcpcheck_ruleset(rs);
4060 return -1;
4061}
4062
4063/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004064int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004065 const char *file, int line)
4066{
4067 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4068 static char *redis_res = "+PONG\r\n";
4069
4070 struct tcpcheck_ruleset *rs = NULL;
4071 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4072 struct tcpcheck_rule *chk;
4073 char *errmsg = NULL;
4074 int err_code = 0;
4075
4076 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4077 err_code |= ERR_WARN;
4078
4079 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4080 goto out;
4081
4082 curpx->options2 &= ~PR_O2_CHK_ANY;
4083 curpx->options2 |= PR_O2_TCPCHK_CHK;
4084
4085 free_tcpcheck_vars(&rules->preset_vars);
4086 rules->list = NULL;
4087 rules->flags = 0;
4088
4089 rs = find_tcpcheck_ruleset("*redis-check");
4090 if (rs)
4091 goto ruleset_found;
4092
4093 rs = create_tcpcheck_ruleset("*redis-check");
4094 if (rs == NULL) {
4095 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4096 goto error;
4097 }
4098
4099 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4100 1, curpx, &rs->rules, file, line, &errmsg);
4101 if (!chk) {
4102 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4103 goto error;
4104 }
4105 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004106 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004107
4108 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4109 "error-status", "L7STS",
4110 "on-error", "%[res.payload(0,0),cut_crlf]",
4111 "on-success", "Redis server is ok",
4112 ""},
4113 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4114 if (!chk) {
4115 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4116 goto error;
4117 }
4118 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004119 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004120
4121 ruleset_found:
4122 rules->list = &rs->rules;
4123 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4124 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4125
4126 out:
4127 free(errmsg);
4128 return err_code;
4129
4130 error:
4131 free_tcpcheck_ruleset(rs);
4132 err_code |= ERR_ALERT | ERR_FATAL;
4133 goto out;
4134}
4135
4136
4137/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004138int 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 +01004139 const char *file, int line)
4140{
4141 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4142 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4143 *
4144 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4145 */
4146 static char sslv3_client_hello[] = {
4147 "16" /* ContentType : 0x16 = Handshake */
4148 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4149 "0079" /* ContentLength : 0x79 bytes after this one */
4150 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4151 "000075" /* HandshakeLength : 0x75 bytes after this one */
4152 "0300" /* Hello Version : 0x0300 = v3 */
4153 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4154 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4155 "00" /* Session ID length : empty (no session ID) */
4156 "004E" /* Cipher Suite Length : 78 bytes after this one */
4157 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4158 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4159 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4160 "000D" "000E" "000F" "0010" /* various bit lengths, */
4161 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4162 "0015" "0016" "0017" "0018"
4163 "0019" "001A" "001B" "002F"
4164 "0030" "0031" "0032" "0033"
4165 "0034" "0035" "0036" "0037"
4166 "0038" "0039" "003A"
4167 "01" /* Compression Length : 0x01 = 1 byte for types */
4168 "00" /* Compression Type : 0x00 = NULL compression */
4169 };
4170
4171 struct tcpcheck_ruleset *rs = NULL;
4172 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4173 struct tcpcheck_rule *chk;
4174 char *errmsg = NULL;
4175 int err_code = 0;
4176
4177 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4178 err_code |= ERR_WARN;
4179
4180 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4181 goto out;
4182
4183 curpx->options2 &= ~PR_O2_CHK_ANY;
4184 curpx->options2 |= PR_O2_TCPCHK_CHK;
4185
4186 free_tcpcheck_vars(&rules->preset_vars);
4187 rules->list = NULL;
4188 rules->flags = 0;
4189
4190 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4191 if (rs)
4192 goto ruleset_found;
4193
4194 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4195 if (rs == NULL) {
4196 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4197 goto error;
4198 }
4199
4200 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4201 1, curpx, &rs->rules, file, line, &errmsg);
4202 if (!chk) {
4203 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4204 goto error;
4205 }
4206 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004207 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004208
4209 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4210 "min-recv", "5", "ok-status", "L6OK",
4211 "error-status", "L6RSP", "tout-status", "L6TOUT",
4212 ""},
4213 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4214 if (!chk) {
4215 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4216 goto error;
4217 }
4218 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004219 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004220
4221 ruleset_found:
4222 rules->list = &rs->rules;
4223 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4224 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4225
4226 out:
4227 free(errmsg);
4228 return err_code;
4229
4230 error:
4231 free_tcpcheck_ruleset(rs);
4232 err_code |= ERR_ALERT | ERR_FATAL;
4233 goto out;
4234}
4235
4236/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004237int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004238 const char *file, int line)
4239{
4240 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4241
4242 struct tcpcheck_ruleset *rs = NULL;
4243 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4244 struct tcpcheck_rule *chk;
4245 struct tcpcheck_var *var = NULL;
4246 char *cmd = NULL, *errmsg = NULL;
4247 int err_code = 0;
4248
4249 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4250 err_code |= ERR_WARN;
4251
4252 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4253 goto out;
4254
4255 curpx->options2 &= ~PR_O2_CHK_ANY;
4256 curpx->options2 |= PR_O2_TCPCHK_CHK;
4257
4258 free_tcpcheck_vars(&rules->preset_vars);
4259 rules->list = NULL;
4260 rules->flags = 0;
4261
4262 cur_arg += 2;
4263 if (*args[cur_arg] && *args[cur_arg+1] &&
4264 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4265 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4266 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4267 if (cmd)
4268 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4269 }
4270 else {
4271 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4272 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4273 cmd = strdup("HELO localhost");
4274 }
4275
4276 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4277 if (cmd == NULL || var == NULL) {
4278 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4279 goto error;
4280 }
4281 var->data.type = SMP_T_STR;
4282 var->data.u.str.area = cmd;
4283 var->data.u.str.data = strlen(cmd);
4284 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004285 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004286 cmd = NULL;
4287 var = NULL;
4288
4289 rs = find_tcpcheck_ruleset("*smtp-check");
4290 if (rs)
4291 goto ruleset_found;
4292
4293 rs = create_tcpcheck_ruleset("*smtp-check");
4294 if (rs == NULL) {
4295 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4296 goto error;
4297 }
4298
4299 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4300 1, curpx, &rs->rules, file, line, &errmsg);
4301 if (!chk) {
4302 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4303 goto error;
4304 }
4305 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004306 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004307
4308 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4309 "min-recv", "4",
4310 "error-status", "L7RSP",
4311 "on-error", "%[res.payload(0,0),cut_crlf]",
4312 ""},
4313 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4314 if (!chk) {
4315 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4316 goto error;
4317 }
4318 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004319 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004320
4321 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4322 "min-recv", "4",
4323 "error-status", "L7STS",
4324 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4325 "status-code", "res.payload(0,3)",
4326 ""},
4327 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4328 if (!chk) {
4329 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4330 goto error;
4331 }
4332 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004333 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004334
4335 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4336 1, curpx, &rs->rules, file, line, &errmsg);
4337 if (!chk) {
4338 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4339 goto error;
4340 }
4341 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004342 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004343
4344 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4345 "min-recv", "4",
4346 "error-status", "L7STS",
4347 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4348 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4349 "status-code", "res.payload(0,3)",
4350 ""},
4351 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4352 if (!chk) {
4353 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4354 goto error;
4355 }
4356 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004357 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004358
4359 ruleset_found:
4360 rules->list = &rs->rules;
4361 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4362 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4363
4364 out:
4365 free(errmsg);
4366 return err_code;
4367
4368 error:
4369 free(cmd);
4370 free(var);
4371 free_tcpcheck_vars(&rules->preset_vars);
4372 free_tcpcheck_ruleset(rs);
4373 err_code |= ERR_ALERT | ERR_FATAL;
4374 goto out;
4375}
4376
4377/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004378int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004379 const char *file, int line)
4380{
4381 static char pgsql_req[] = {
4382 "%[var(check.plen),htonl,hex]" /* The packet length*/
4383 "00030000" /* the version 3.0 */
4384 "7573657200" /* "user" key */
4385 "%[var(check.username),hex]00" /* the username */
4386 "00"
4387 };
4388
4389 struct tcpcheck_ruleset *rs = NULL;
4390 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4391 struct tcpcheck_rule *chk;
4392 struct tcpcheck_var *var = NULL;
4393 char *user = NULL, *errmsg = NULL;
4394 size_t packetlen = 0;
4395 int err_code = 0;
4396
4397 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4398 err_code |= ERR_WARN;
4399
4400 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4401 goto out;
4402
4403 curpx->options2 &= ~PR_O2_CHK_ANY;
4404 curpx->options2 |= PR_O2_TCPCHK_CHK;
4405
4406 free_tcpcheck_vars(&rules->preset_vars);
4407 rules->list = NULL;
4408 rules->flags = 0;
4409
4410 cur_arg += 2;
4411 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4412 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4413 file, line, args[0], args[1]);
4414 goto error;
4415 }
4416 if (strcmp(args[cur_arg], "user") == 0) {
4417 packetlen = 15 + strlen(args[cur_arg+1]);
4418 user = strdup(args[cur_arg+1]);
4419
4420 var = create_tcpcheck_var(ist("check.username"));
4421 if (user == NULL || var == NULL) {
4422 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4423 goto error;
4424 }
4425 var->data.type = SMP_T_STR;
4426 var->data.u.str.area = user;
4427 var->data.u.str.data = strlen(user);
4428 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004429 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004430 user = NULL;
4431 var = NULL;
4432
4433 var = create_tcpcheck_var(ist("check.plen"));
4434 if (var == NULL) {
4435 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4436 goto error;
4437 }
4438 var->data.type = SMP_T_SINT;
4439 var->data.u.sint = packetlen;
4440 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004441 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004442 var = NULL;
4443 }
4444 else {
4445 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4446 file, line, args[0], args[1]);
4447 goto error;
4448 }
4449
4450 rs = find_tcpcheck_ruleset("*pgsql-check");
4451 if (rs)
4452 goto ruleset_found;
4453
4454 rs = create_tcpcheck_ruleset("*pgsql-check");
4455 if (rs == NULL) {
4456 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4457 goto error;
4458 }
4459
4460 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4461 1, curpx, &rs->rules, file, line, &errmsg);
4462 if (!chk) {
4463 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4464 goto error;
4465 }
4466 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004467 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004468
4469 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4470 1, curpx, &rs->rules, file, line, &errmsg);
4471 if (!chk) {
4472 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4473 goto error;
4474 }
4475 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004476 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004477
4478 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4479 "min-recv", "5",
4480 "error-status", "L7RSP",
4481 "on-error", "%[res.payload(6,0)]",
4482 ""},
4483 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4484 if (!chk) {
4485 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4486 goto error;
4487 }
4488 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004489 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004490
4491 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4492 "min-recv", "9",
4493 "error-status", "L7STS",
4494 "on-success", "PostgreSQL server is ok",
4495 "on-error", "PostgreSQL unknown error",
4496 ""},
4497 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4498 if (!chk) {
4499 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4500 goto error;
4501 }
4502 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004503 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004504
4505 ruleset_found:
4506 rules->list = &rs->rules;
4507 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4508 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4509
4510 out:
4511 free(errmsg);
4512 return err_code;
4513
4514 error:
4515 free(user);
4516 free(var);
4517 free_tcpcheck_vars(&rules->preset_vars);
4518 free_tcpcheck_ruleset(rs);
4519 err_code |= ERR_ALERT | ERR_FATAL;
4520 goto out;
4521}
4522
4523
4524/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004525int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004526 const char *file, int line)
4527{
4528 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4529 * const char mysql40_client_auth_pkt[] = {
4530 * "\x0e\x00\x00" // packet length
4531 * "\x01" // packet number
4532 * "\x00\x00" // client capabilities
4533 * "\x00\x00\x01" // max packet
4534 * "haproxy\x00" // username (null terminated string)
4535 * "\x00" // filler (always 0x00)
4536 * "\x01\x00\x00" // packet length
4537 * "\x00" // packet number
4538 * "\x01" // COM_QUIT command
4539 * };
4540 */
4541 static char mysql40_rsname[] = "*mysql40-check";
4542 static char mysql40_req[] = {
4543 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4544 "0080" /* client capabilities */
4545 "000001" /* max packet */
4546 "%[var(check.username),hex]00" /* the username */
4547 "00" /* filler (always 0x00) */
4548 "010000" /* packet length*/
4549 "00" /* sequence ID */
4550 "01" /* COM_QUIT command */
4551 };
4552
4553 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4554 * const char mysql41_client_auth_pkt[] = {
4555 * "\x0e\x00\x00\" // packet length
4556 * "\x01" // packet number
4557 * "\x00\x00\x00\x00" // client capabilities
4558 * "\x00\x00\x00\x01" // max packet
4559 * "\x21" // character set (UTF-8)
4560 * char[23] // All zeroes
4561 * "haproxy\x00" // username (null terminated string)
4562 * "\x00" // filler (always 0x00)
4563 * "\x01\x00\x00" // packet length
4564 * "\x00" // packet number
4565 * "\x01" // COM_QUIT command
4566 * };
4567 */
4568 static char mysql41_rsname[] = "*mysql41-check";
4569 static char mysql41_req[] = {
4570 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4571 "00820000" /* client capabilities */
4572 "00800001" /* max packet */
4573 "21" /* character set (UTF-8) */
4574 "000000000000000000000000" /* 23 bytes, al zeroes */
4575 "0000000000000000000000"
4576 "%[var(check.username),hex]00" /* the username */
4577 "00" /* filler (always 0x00) */
4578 "010000" /* packet length*/
4579 "00" /* sequence ID */
4580 "01" /* COM_QUIT command */
4581 };
4582
4583 struct tcpcheck_ruleset *rs = NULL;
4584 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4585 struct tcpcheck_rule *chk;
4586 struct tcpcheck_var *var = NULL;
4587 char *mysql_rsname = "*mysql-check";
4588 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4589 int index = 0, err_code = 0;
4590
4591 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4592 err_code |= ERR_WARN;
4593
4594 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4595 goto out;
4596
4597 curpx->options2 &= ~PR_O2_CHK_ANY;
4598 curpx->options2 |= PR_O2_TCPCHK_CHK;
4599
4600 free_tcpcheck_vars(&rules->preset_vars);
4601 rules->list = NULL;
4602 rules->flags = 0;
4603
4604 cur_arg += 2;
4605 if (*args[cur_arg]) {
4606 int packetlen, userlen;
4607
4608 if (strcmp(args[cur_arg], "user") != 0) {
4609 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4610 file, line, args[0], args[1], args[cur_arg]);
4611 goto error;
4612 }
4613
4614 if (*(args[cur_arg+1]) == 0) {
4615 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4616 file, line, args[0], args[1], args[cur_arg]);
4617 goto error;
4618 }
4619
4620 hdr = calloc(4, sizeof(*hdr));
4621 user = strdup(args[cur_arg+1]);
4622 userlen = strlen(args[cur_arg+1]);
4623
4624 if (hdr == NULL || user == NULL) {
4625 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4626 goto error;
4627 }
4628
4629 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4630 packetlen = userlen + 7 + 27;
4631 mysql_req = mysql41_req;
4632 mysql_rsname = mysql41_rsname;
4633 }
4634 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4635 packetlen = userlen + 7;
4636 mysql_req = mysql40_req;
4637 mysql_rsname = mysql40_rsname;
4638 }
4639 else {
4640 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4641 file, line, args[cur_arg], args[cur_arg+2]);
4642 goto error;
4643 }
4644
4645 hdr[0] = (unsigned char)(packetlen & 0xff);
4646 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4647 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4648 hdr[3] = 1;
4649
4650 var = create_tcpcheck_var(ist("check.header"));
4651 if (var == NULL) {
4652 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4653 goto error;
4654 }
4655 var->data.type = SMP_T_STR;
4656 var->data.u.str.area = hdr;
4657 var->data.u.str.data = 4;
4658 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004659 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004660 hdr = NULL;
4661 var = NULL;
4662
4663 var = create_tcpcheck_var(ist("check.username"));
4664 if (var == NULL) {
4665 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4666 goto error;
4667 }
4668 var->data.type = SMP_T_STR;
4669 var->data.u.str.area = user;
4670 var->data.u.str.data = strlen(user);
4671 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004672 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004673 user = NULL;
4674 var = NULL;
4675 }
4676
4677 rs = find_tcpcheck_ruleset(mysql_rsname);
4678 if (rs)
4679 goto ruleset_found;
4680
4681 rs = create_tcpcheck_ruleset(mysql_rsname);
4682 if (rs == NULL) {
4683 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4684 goto error;
4685 }
4686
4687 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4688 1, curpx, &rs->rules, file, line, &errmsg);
4689 if (!chk) {
4690 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4691 goto error;
4692 }
4693 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004694 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004695
4696 if (mysql_req) {
4697 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4698 1, curpx, &rs->rules, file, line, &errmsg);
4699 if (!chk) {
4700 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4701 goto error;
4702 }
4703 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004704 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004705 }
4706
4707 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4708 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4709 if (!chk) {
4710 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4711 goto error;
4712 }
4713 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4714 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004715 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004716
4717 if (mysql_req) {
4718 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4719 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4720 if (!chk) {
4721 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4722 goto error;
4723 }
4724 chk->expect.custom = tcpcheck_mysql_expect_ok;
4725 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004726 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004727 }
4728
4729 ruleset_found:
4730 rules->list = &rs->rules;
4731 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4732 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4733
4734 out:
4735 free(errmsg);
4736 return err_code;
4737
4738 error:
4739 free(hdr);
4740 free(user);
4741 free(var);
4742 free_tcpcheck_vars(&rules->preset_vars);
4743 free_tcpcheck_ruleset(rs);
4744 err_code |= ERR_ALERT | ERR_FATAL;
4745 goto out;
4746}
4747
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004748int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004749 const char *file, int line)
4750{
4751 static char *ldap_req = "300C020101600702010304008000";
4752
4753 struct tcpcheck_ruleset *rs = NULL;
4754 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4755 struct tcpcheck_rule *chk;
4756 char *errmsg = NULL;
4757 int err_code = 0;
4758
4759 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4760 err_code |= ERR_WARN;
4761
4762 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4763 goto out;
4764
4765 curpx->options2 &= ~PR_O2_CHK_ANY;
4766 curpx->options2 |= PR_O2_TCPCHK_CHK;
4767
4768 free_tcpcheck_vars(&rules->preset_vars);
4769 rules->list = NULL;
4770 rules->flags = 0;
4771
4772 rs = find_tcpcheck_ruleset("*ldap-check");
4773 if (rs)
4774 goto ruleset_found;
4775
4776 rs = create_tcpcheck_ruleset("*ldap-check");
4777 if (rs == NULL) {
4778 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4779 goto error;
4780 }
4781
4782 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4783 1, curpx, &rs->rules, file, line, &errmsg);
4784 if (!chk) {
4785 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4786 goto error;
4787 }
4788 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004789 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004790
4791 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4792 "min-recv", "14",
4793 "on-error", "Not LDAPv3 protocol",
4794 ""},
4795 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4796 if (!chk) {
4797 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4798 goto error;
4799 }
4800 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004801 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004802
4803 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4804 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4805 if (!chk) {
4806 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4807 goto error;
4808 }
4809 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4810 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004811 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004812
4813 ruleset_found:
4814 rules->list = &rs->rules;
4815 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4816 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4817
4818 out:
4819 free(errmsg);
4820 return err_code;
4821
4822 error:
4823 free_tcpcheck_ruleset(rs);
4824 err_code |= ERR_ALERT | ERR_FATAL;
4825 goto out;
4826}
4827
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004828int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004829 const char *file, int line)
4830{
4831 struct tcpcheck_ruleset *rs = NULL;
4832 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4833 struct tcpcheck_rule *chk;
4834 char *spop_req = NULL;
4835 char *errmsg = NULL;
4836 int spop_len = 0, err_code = 0;
4837
4838 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4839 err_code |= ERR_WARN;
4840
4841 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4842 goto out;
4843
4844 curpx->options2 &= ~PR_O2_CHK_ANY;
4845 curpx->options2 |= PR_O2_TCPCHK_CHK;
4846
4847 free_tcpcheck_vars(&rules->preset_vars);
4848 rules->list = NULL;
4849 rules->flags = 0;
4850
4851
4852 rs = find_tcpcheck_ruleset("*spop-check");
4853 if (rs)
4854 goto ruleset_found;
4855
4856 rs = create_tcpcheck_ruleset("*spop-check");
4857 if (rs == NULL) {
4858 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4859 goto error;
4860 }
4861
4862 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4863 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4864 goto error;
4865 }
4866 chunk_reset(&trash);
4867 dump_binary(&trash, spop_req, spop_len);
4868 trash.area[trash.data] = '\0';
4869
4870 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4871 1, curpx, &rs->rules, file, line, &errmsg);
4872 if (!chk) {
4873 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4874 goto error;
4875 }
4876 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004877 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004878
4879 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4880 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4881 if (!chk) {
4882 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4883 goto error;
4884 }
4885 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4886 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004887 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004888
4889 ruleset_found:
4890 rules->list = &rs->rules;
4891 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4892 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4893
4894 out:
4895 free(spop_req);
4896 free(errmsg);
4897 return err_code;
4898
4899 error:
4900 free_tcpcheck_ruleset(rs);
4901 err_code |= ERR_ALERT | ERR_FATAL;
4902 goto out;
4903}
4904
4905
4906static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4907{
4908 struct tcpcheck_rule *chk = NULL;
4909 struct tcpcheck_http_hdr *hdr = NULL;
4910 char *meth = NULL, *uri = NULL, *vsn = NULL;
4911 char *hdrs, *body;
4912
4913 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4914 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4915 if (hdrs == body)
4916 hdrs = NULL;
4917 if (hdrs) {
4918 *hdrs = '\0';
4919 hdrs +=2;
4920 }
4921 if (body) {
4922 *body = '\0';
4923 body += 4;
4924 }
4925 if (hdrs || body) {
4926 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4927 " Please, consider to use 'http-check send' directive instead.");
4928 }
4929
4930 chk = calloc(1, sizeof(*chk));
4931 if (!chk) {
4932 memprintf(errmsg, "out of memory");
4933 goto error;
4934 }
4935 chk->action = TCPCHK_ACT_SEND;
4936 chk->send.type = TCPCHK_SEND_HTTP;
4937 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4938 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4939 LIST_INIT(&chk->send.http.hdrs);
4940
4941 /* Copy the method, uri and version */
4942 if (*args[cur_arg]) {
4943 if (!*args[cur_arg+1])
4944 uri = args[cur_arg];
4945 else
4946 meth = args[cur_arg];
4947 }
4948 if (*args[cur_arg+1])
4949 uri = args[cur_arg+1];
4950 if (*args[cur_arg+2])
4951 vsn = args[cur_arg+2];
4952
4953 if (meth) {
4954 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4955 chk->send.http.meth.str.area = strdup(meth);
4956 chk->send.http.meth.str.data = strlen(meth);
4957 if (!chk->send.http.meth.str.area) {
4958 memprintf(errmsg, "out of memory");
4959 goto error;
4960 }
4961 }
4962 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004963 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004964 if (!isttest(chk->send.http.uri)) {
4965 memprintf(errmsg, "out of memory");
4966 goto error;
4967 }
4968 }
4969 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004970 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004971 if (!isttest(chk->send.http.vsn)) {
4972 memprintf(errmsg, "out of memory");
4973 goto error;
4974 }
4975 }
4976
4977 /* Copy the header */
4978 if (hdrs) {
4979 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4980 struct h1m h1m;
4981 int i, ret;
4982
4983 /* Build and parse the request */
4984 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4985
4986 h1m.flags = H1_MF_HDRS_ONLY;
4987 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4988 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4989 &h1m, NULL);
4990 if (ret <= 0) {
4991 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4992 goto error;
4993 }
4994
4995 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4996 hdr = calloc(1, sizeof(*hdr));
4997 if (!hdr) {
4998 memprintf(errmsg, "out of memory");
4999 goto error;
5000 }
5001 LIST_INIT(&hdr->value);
5002 hdr->name = istdup(tmp_hdrs[i].n);
5003 if (!hdr->name.ptr) {
5004 memprintf(errmsg, "out of memory");
5005 goto error;
5006 }
5007
5008 ist0(tmp_hdrs[i].v);
5009 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5010 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005011 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005012 }
5013 }
5014
5015 /* Copy the body */
5016 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005017 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005018 if (!isttest(chk->send.http.body)) {
5019 memprintf(errmsg, "out of memory");
5020 goto error;
5021 }
5022 }
5023
5024 return chk;
5025
5026 error:
5027 free_tcpcheck_http_hdr(hdr);
5028 free_tcpcheck(chk, 0);
5029 return NULL;
5030}
5031
5032/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005033int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005034 const char *file, int line)
5035{
5036 struct tcpcheck_ruleset *rs = NULL;
5037 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5038 struct tcpcheck_rule *chk;
5039 char *errmsg = NULL;
5040 int err_code = 0;
5041
5042 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5043 err_code |= ERR_WARN;
5044
5045 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5046 goto out;
5047
5048 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5049 if (!chk) {
5050 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5051 goto error;
5052 }
5053 if (errmsg) {
5054 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5055 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005056 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005057 }
5058
5059 no_request:
5060 curpx->options2 &= ~PR_O2_CHK_ANY;
5061 curpx->options2 |= PR_O2_TCPCHK_CHK;
5062
5063 free_tcpcheck_vars(&rules->preset_vars);
5064 rules->list = NULL;
5065 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5066
5067 /* Deduce the ruleset name from the proxy info */
5068 chunk_printf(&trash, "*http-check-%s_%s-%d",
5069 ((curpx == defpx) ? "defaults" : curpx->id),
5070 curpx->conf.file, curpx->conf.line);
5071
5072 rs = find_tcpcheck_ruleset(b_orig(&trash));
5073 if (rs == NULL) {
5074 rs = create_tcpcheck_ruleset(b_orig(&trash));
5075 if (rs == NULL) {
5076 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5077 goto error;
5078 }
5079 }
5080
5081 rules->list = &rs->rules;
5082 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5083 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5084 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5085 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5086 rules->list = NULL;
5087 goto error;
5088 }
5089
5090 out:
5091 free(errmsg);
5092 return err_code;
5093
5094 error:
5095 free_tcpcheck_ruleset(rs);
5096 free_tcpcheck(chk, 0);
5097 err_code |= ERR_ALERT | ERR_FATAL;
5098 goto out;
5099}
5100
5101/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005102int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005103 const char *file, int line)
5104{
5105 struct tcpcheck_ruleset *rs = NULL;
5106 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5107 int err_code = 0;
5108
5109 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5110 err_code |= ERR_WARN;
5111
5112 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5113 goto out;
5114
5115 curpx->options2 &= ~PR_O2_CHK_ANY;
5116 curpx->options2 |= PR_O2_TCPCHK_CHK;
5117
5118 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5119 /* If a tcp-check rulesset is already set, do nothing */
5120 if (rules->list)
5121 goto out;
5122
5123 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5124 * get it.
5125 */
5126 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5127 goto curpx_ruleset;
5128
5129 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5130 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5131 rs = find_tcpcheck_ruleset(b_orig(&trash));
5132 if (rs)
5133 goto ruleset_found;
5134 }
5135
5136 curpx_ruleset:
5137 /* Deduce the ruleset name from the proxy info */
5138 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5139 ((curpx == defpx) ? "defaults" : curpx->id),
5140 curpx->conf.file, curpx->conf.line);
5141
5142 rs = find_tcpcheck_ruleset(b_orig(&trash));
5143 if (rs == NULL) {
5144 rs = create_tcpcheck_ruleset(b_orig(&trash));
5145 if (rs == NULL) {
5146 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5147 goto error;
5148 }
5149 }
5150
5151 ruleset_found:
5152 free_tcpcheck_vars(&rules->preset_vars);
5153 rules->list = &rs->rules;
5154 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5155 rules->flags |= TCPCHK_RULES_TCP_CHK;
5156
5157 out:
5158 return err_code;
5159
5160 error:
5161 err_code |= ERR_ALERT | ERR_FATAL;
5162 goto out;
5163}
5164
Willy Tarreau51cd5952020-06-05 12:25:38 +02005165static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005166 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005167 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5168 { 0, NULL, NULL },
5169}};
5170
5171REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5172REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5173REGISTER_POST_DEINIT(deinit_tcpchecks);
5174INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);