blob: b7827967325dca15e6778b7fd889e04cc182f972 [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 Faulet8a0e5f82021-09-16 16:01:09 +020051#include <haproxy/net_helper.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020052#include <haproxy/protocol.h>
53#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020054#include <haproxy/regex.h>
55#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020056#include <haproxy/server.h>
57#include <haproxy/ssl_sock.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020058#include <haproxy/task.h>
59#include <haproxy/tcpcheck.h>
Willy Tarreau9310f482021-10-06 16:18:40 +020060#include <haproxy/ticks.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020061#include <haproxy/tools.h>
Christopher Faulet147b8c92021-04-10 09:00:38 +020062#include <haproxy/trace.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020063#include <haproxy/vars.h>
64
65
Christopher Faulet147b8c92021-04-10 09:00:38 +020066#define TRACE_SOURCE &trace_check
67
Willy Tarreau51cd5952020-06-05 12:25:38 +020068/* Global tree to share all tcp-checks */
69struct eb_root shared_tcpchecks = EB_ROOT;
70
71
72DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
73
74/**************************************************************************/
75/*************** Init/deinit tcp-check rules and ruleset ******************/
76/**************************************************************************/
77/* Releases memory allocated for a log-format string */
78static void free_tcpcheck_fmt(struct list *fmt)
79{
80 struct logformat_node *lf, *lfb;
81
82 list_for_each_entry_safe(lf, lfb, fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +020083 LIST_DELETE(&lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +020084 release_sample_expr(lf->expr);
85 free(lf->arg);
86 free(lf);
87 }
88}
89
90/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
91void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
92{
93 if (!hdr)
94 return;
95
96 free_tcpcheck_fmt(&hdr->value);
97 istfree(&hdr->name);
98 free(hdr);
99}
100
101/* Releases memory allocated for an HTTP header list used in a tcp-check send
102 * rule
103 */
104static void free_tcpcheck_http_hdrs(struct list *hdrs)
105{
106 struct tcpcheck_http_hdr *hdr, *bhdr;
107
108 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200109 LIST_DELETE(&hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200110 free_tcpcheck_http_hdr(hdr);
111 }
112}
113
114/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
115 * tcp-check was allocated using a memory pool (it is used to instantiate email
116 * alerts).
117 */
118void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
119{
120 if (!rule)
121 return;
122
123 free(rule->comment);
124 switch (rule->action) {
125 case TCPCHK_ACT_SEND:
126 switch (rule->send.type) {
127 case TCPCHK_SEND_STRING:
128 case TCPCHK_SEND_BINARY:
129 istfree(&rule->send.data);
130 break;
131 case TCPCHK_SEND_STRING_LF:
132 case TCPCHK_SEND_BINARY_LF:
133 free_tcpcheck_fmt(&rule->send.fmt);
134 break;
135 case TCPCHK_SEND_HTTP:
136 free(rule->send.http.meth.str.area);
137 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
138 istfree(&rule->send.http.uri);
139 else
140 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
141 istfree(&rule->send.http.vsn);
142 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
143 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
144 istfree(&rule->send.http.body);
145 else
146 free_tcpcheck_fmt(&rule->send.http.body_fmt);
147 break;
148 case TCPCHK_SEND_UNDEF:
149 break;
150 }
151 break;
152 case TCPCHK_ACT_EXPECT:
153 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
154 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
155 release_sample_expr(rule->expect.status_expr);
156 switch (rule->expect.type) {
157 case TCPCHK_EXPECT_HTTP_STATUS:
158 free(rule->expect.codes.codes);
159 break;
160 case TCPCHK_EXPECT_STRING:
161 case TCPCHK_EXPECT_BINARY:
162 case TCPCHK_EXPECT_HTTP_BODY:
163 istfree(&rule->expect.data);
164 break;
165 case TCPCHK_EXPECT_STRING_REGEX:
166 case TCPCHK_EXPECT_BINARY_REGEX:
167 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
168 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
169 regex_free(rule->expect.regex);
170 break;
171 case TCPCHK_EXPECT_STRING_LF:
172 case TCPCHK_EXPECT_BINARY_LF:
173 case TCPCHK_EXPECT_HTTP_BODY_LF:
174 free_tcpcheck_fmt(&rule->expect.fmt);
175 break;
176 case TCPCHK_EXPECT_HTTP_HEADER:
177 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
178 regex_free(rule->expect.hdr.name_re);
179 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
180 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
181 else
182 istfree(&rule->expect.hdr.name);
183
184 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
185 regex_free(rule->expect.hdr.value_re);
186 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
187 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
188 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
189 istfree(&rule->expect.hdr.value);
190 break;
191 case TCPCHK_EXPECT_CUSTOM:
192 case TCPCHK_EXPECT_UNDEF:
193 break;
194 }
195 break;
196 case TCPCHK_ACT_CONNECT:
197 free(rule->connect.sni);
198 free(rule->connect.alpn);
199 release_sample_expr(rule->connect.port_expr);
200 break;
201 case TCPCHK_ACT_COMMENT:
202 break;
203 case TCPCHK_ACT_ACTION_KW:
204 free(rule->action_kw.rule);
205 break;
206 }
207
208 if (in_pool)
209 pool_free(pool_head_tcpcheck_rule, rule);
210 else
211 free(rule);
212}
213
214/* Creates a tcp-check variable used in preset variables before executing a
215 * tcp-check ruleset.
216 */
217struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
218{
219 struct tcpcheck_var *var = NULL;
220
221 var = calloc(1, sizeof(*var));
222 if (var == NULL)
223 return NULL;
224
225 var->name = istdup(name);
226 if (!isttest(var->name)) {
227 free(var);
228 return NULL;
229 }
230
231 LIST_INIT(&var->list);
232 return var;
233}
234
235/* Releases memory allocated for a preset tcp-check variable */
236void free_tcpcheck_var(struct tcpcheck_var *var)
237{
238 if (!var)
239 return;
240
241 istfree(&var->name);
242 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
243 free(var->data.u.str.area);
244 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
245 free(var->data.u.meth.str.area);
246 free(var);
247}
248
249/* Releases a list of preset tcp-check variables */
250void free_tcpcheck_vars(struct list *vars)
251{
252 struct tcpcheck_var *var, *back;
253
254 list_for_each_entry_safe(var, back, vars, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200255 LIST_DELETE(&var->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200256 free_tcpcheck_var(var);
257 }
258}
259
260/* Duplicate a list of preset tcp-check variables */
Willy Tarreau09f2e772021-02-12 08:42:30 +0100261int dup_tcpcheck_vars(struct list *dst, const struct list *src)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200262{
Willy Tarreau09f2e772021-02-12 08:42:30 +0100263 const struct tcpcheck_var *var;
264 struct tcpcheck_var *new = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200265
266 list_for_each_entry(var, src, list) {
267 new = create_tcpcheck_var(var->name);
268 if (!new)
269 goto error;
270 new->data.type = var->data.type;
271 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
272 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
273 goto error;
274 if (var->data.type == SMP_T_STR)
275 new->data.u.str.area[new->data.u.str.data] = 0;
276 }
277 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
278 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
279 goto error;
280 new->data.u.str.area[new->data.u.str.data] = 0;
281 new->data.u.meth.meth = var->data.u.meth.meth;
282 }
283 else
284 new->data.u = var->data.u;
Willy Tarreau2b718102021-04-21 07:32:39 +0200285 LIST_APPEND(dst, &new->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200286 }
287 return 1;
288
289 error:
290 free(new);
291 return 0;
292}
293
294/* Looks for a shared tcp-check ruleset given its name. */
295struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
296{
297 struct tcpcheck_ruleset *rs;
298 struct ebpt_node *node;
299
300 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
301 if (node) {
302 rs = container_of(node, typeof(*rs), node);
303 return rs;
304 }
305 return NULL;
306}
307
308/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks
309 * tree.
310 */
311struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
312{
313 struct tcpcheck_ruleset *rs;
314
315 rs = calloc(1, sizeof(*rs));
316 if (rs == NULL)
317 return NULL;
318
319 rs->node.key = strdup(name);
320 if (rs->node.key == NULL) {
321 free(rs);
322 return NULL;
323 }
324
325 LIST_INIT(&rs->rules);
326 ebis_insert(&shared_tcpchecks, &rs->node);
327 return rs;
328}
329
330/* Releases memory allocated by a tcp-check ruleset. */
331void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
332{
333 struct tcpcheck_rule *r, *rb;
334
335 if (!rs)
336 return;
337
338 ebpt_delete(&rs->node);
339 free(rs->node.key);
340 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200341 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200342 free_tcpcheck(r, 0);
343 }
344 free(rs);
345}
346
347
348/**************************************************************************/
349/**************** Everything about tcp-checks execution *******************/
350/**************************************************************************/
351/* Returns the id of a step in a tcp-check ruleset */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200352int tcpcheck_get_step_id(const struct check *check, const struct tcpcheck_rule *rule)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200353{
354 if (!rule)
355 rule = check->current_step;
356
357 /* no last started step => first step */
358 if (!rule)
359 return 1;
360
361 /* last step is the first implicit connect */
362 if (rule->index == 0 &&
363 rule->action == TCPCHK_ACT_CONNECT &&
364 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
365 return 0;
366
367 return rule->index + 1;
368}
369
370/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
371 * NULL if none was found.
372 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200373struct tcpcheck_rule *get_first_tcpcheck_rule(const struct tcpcheck_rules *rules)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200374{
375 struct tcpcheck_rule *r;
376
377 list_for_each_entry(r, rules->list, list) {
378 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
379 return r;
380 }
381 return NULL;
382}
383
384/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
385 * NULL if none was found.
386 */
387static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
388{
389 struct tcpcheck_rule *r;
390
391 list_for_each_entry_rev(r, rules->list, list) {
392 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
393 return r;
394 }
395 return NULL;
396}
397
398/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
399 * <start> or NULL if non was found. If <start> is NULL, it relies on
400 * get_first_tcpcheck_rule().
401 */
402static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
403{
404 struct tcpcheck_rule *r;
405
406 if (!start)
407 return get_first_tcpcheck_rule(rules);
408
409 r = LIST_NEXT(&start->list, typeof(r), list);
410 list_for_each_entry_from(r, rules->list, list) {
411 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
412 return r;
413 }
414 return NULL;
415}
416
417
418/* Creates info message when a tcp-check healthcheck fails on an expect rule */
419static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
420 int match, struct ist info)
421{
422 struct sample *smp;
423
424 /* Follows these step to produce the info message:
425 * 1. if info field is already provided, copy it
426 * 2. if the expect rule provides an onerror log-format string,
427 * use it to produce the message
428 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
429 * 4. Otherwise produce the generic tcp-check info message
430 */
431 if (istlen(info)) {
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 Faulet8a0e5f82021-09-16 16:01:09 +0200660 char *ptr;
661 unsigned short nbytes = 0;
662 size_t msglen = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200663
Christopher Faulet147b8c92021-04-10 09:00:38 +0200664 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
665
Willy Tarreau51cd5952020-06-05 12:25:38 +0200666 /* Check if the server speaks LDAP (ASN.1/BER)
667 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
668 * http://tools.ietf.org/html/rfc4511
669 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200670 ptr = b_head(&check->bi) + 1;
671
Willy Tarreau51cd5952020-06-05 12:25:38 +0200672 /* size of LDAPMessage */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200673 if (*ptr & 0x80) {
674 /* For message size encoded on several bytes, we only handle
675 * size encoded on 2 or 4 bytes. There is no reason to make this
676 * part to complex because only Active Directory is known to
677 * encode BindReponse length on 4 bytes.
678 */
679 nbytes = (*ptr & 0x7f);
680 if (b_data(&check->bi) < 1 + nbytes)
681 goto too_short;
682 switch (nbytes) {
683 case 4: msglen = read_n32(ptr+1); break;
684 case 2: msglen = read_n16(ptr+1); break;
685 default:
686 status = HCHK_STATUS_L7RSP;
687 desc = ist("Not LDAPv3 protocol");
688 goto error;
689 }
690 }
691 else
692 msglen = *ptr;
693 ptr += 1 + nbytes;
694
695 if (b_data(&check->bi) < 2 + nbytes + msglen)
696 goto too_short;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200697
698 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
699 * messageID: 0x02 0x01 0x01: INTEGER 1
700 * protocolOp: 0x61: bindResponse
701 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200702 if (memcmp(ptr, "\x02\x01\x01\x61", 4) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200703 status = HCHK_STATUS_L7RSP;
704 desc = ist("Not LDAPv3 protocol");
705 goto error;
706 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200707 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200708
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200709 /* skip size of bindResponse */
710 nbytes = 0;
711 if (*ptr & 0x80)
712 nbytes = (*ptr & 0x7f);
713 ptr += 1 + nbytes;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200714
715 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
716 * ldapResult: 0x0a 0x01: ENUMERATION
717 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200718 if (memcmp(ptr, "\x0a\x01", 2) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200719 status = HCHK_STATUS_L7RSP;
720 desc = ist("Not LDAPv3 protocol");
721 goto error;
722 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200723 ptr += 2;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200724
725 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
726 * resultCode
727 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200728 check->code = *ptr;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200729 if (check->code) {
730 status = HCHK_STATUS_L7STS;
731 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
732 goto error;
733 }
734
735 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
736 set_server_check_status(check, status, "Success");
737
738 out:
739 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200740 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200741 return ret;
742
743 error:
744 ret = TCPCHK_EVAL_STOP;
745 msg = alloc_trash_chunk();
746 if (msg)
747 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
748 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
749 goto out;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200750
751 too_short:
752 if (!last_read)
753 goto wait_more_data;
754 /* invalid length or truncated response */
755 status = HCHK_STATUS_L7RSP;
756 goto error;
757
758 wait_more_data:
759 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
760 ret = TCPCHK_EVAL_WAIT;
761 goto out;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200762}
763
764/* Custom tcp-check expect function to parse and validate the SPOP hello agent
765 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
766 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
767 */
768enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
769{
770 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
771 enum healthcheck_status status;
772 struct buffer *msg = NULL;
773 struct ist desc = IST_NULL;
774 unsigned int framesz;
775
Christopher Faulet147b8c92021-04-10 09:00:38 +0200776 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200777
778 memcpy(&framesz, b_head(&check->bi), 4);
779 framesz = ntohl(framesz);
780
781 if (!last_read && b_data(&check->bi) < (4+framesz))
782 goto wait_more_data;
783
784 memset(b_orig(&trash), 0, b_size(&trash));
785 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
786 status = HCHK_STATUS_L7RSP;
787 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
788 goto error;
789 }
790
791 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
792 set_server_check_status(check, status, "SPOA server is ok");
793
794 out:
795 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200796 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200797 return ret;
798
799 error:
800 ret = TCPCHK_EVAL_STOP;
801 msg = alloc_trash_chunk();
802 if (msg)
803 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
804 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
805 goto out;
806
807 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200808 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200809 ret = TCPCHK_EVAL_WAIT;
810 goto out;
811}
812
813/* Custom tcp-check expect function to parse and validate the agent-check
814 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
815 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
816 */
817enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
818{
819 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
820 enum healthcheck_status status = HCHK_STATUS_CHECKED;
821 const char *hs = NULL; /* health status */
822 const char *as = NULL; /* admin status */
823 const char *ps = NULL; /* performance status */
824 const char *cs = NULL; /* maxconn */
825 const char *err = NULL; /* first error to report */
826 const char *wrn = NULL; /* first warning to report */
827 char *cmd, *p;
828
Christopher Faulet147b8c92021-04-10 09:00:38 +0200829 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
830
Willy Tarreau51cd5952020-06-05 12:25:38 +0200831 /* We're getting an agent check response. The agent could
832 * have been disabled in the mean time with a long check
833 * still pending. It is important that we ignore the whole
834 * response.
835 */
836 if (!(check->state & CHK_ST_ENABLED))
837 goto out;
838
839 /* The agent supports strings made of a single line ended by the
840 * first CR ('\r') or LF ('\n'). This line is composed of words
841 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
842 * line may optionally contained a description of a state change
843 * after a sharp ('#'), which is only considered if a health state
844 * is announced.
845 *
846 * Words may be composed of :
847 * - a numeric weight suffixed by the percent character ('%').
848 * - a health status among "up", "down", "stopped", and "fail".
849 * - an admin status among "ready", "drain", "maint".
850 *
851 * These words may appear in any order. If multiple words of the
852 * same category appear, the last one wins.
853 */
854
855 p = b_head(&check->bi);
856 while (*p && *p != '\n' && *p != '\r')
857 p++;
858
859 if (!*p) {
860 if (!last_read)
861 goto wait_more_data;
862
863 /* at least inform the admin that the agent is mis-behaving */
864 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
865 goto out;
866 }
867
868 *p = 0;
869 cmd = b_head(&check->bi);
870
871 while (*cmd) {
872 /* look for next word */
873 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
874 cmd++;
875 continue;
876 }
877
878 if (*cmd == '#') {
879 /* this is the beginning of a health status description,
880 * skip the sharp and blanks.
881 */
882 cmd++;
883 while (*cmd == '\t' || *cmd == ' ')
884 cmd++;
885 break;
886 }
887
888 /* find the end of the word so that we have a null-terminated
889 * word between <cmd> and <p>.
890 */
891 p = cmd + 1;
892 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
893 p++;
894 if (*p)
895 *p++ = 0;
896
897 /* first, health statuses */
898 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100899 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200900 status = HCHK_STATUS_L7OKD;
901 hs = cmd;
902 }
903 else if (strcasecmp(cmd, "down") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100904 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200905 status = HCHK_STATUS_L7STS;
906 hs = cmd;
907 }
908 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100909 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200910 status = HCHK_STATUS_L7STS;
911 hs = cmd;
912 }
913 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100914 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200915 status = HCHK_STATUS_L7STS;
916 hs = cmd;
917 }
918 /* admin statuses */
919 else if (strcasecmp(cmd, "ready") == 0) {
920 as = cmd;
921 }
922 else if (strcasecmp(cmd, "drain") == 0) {
923 as = cmd;
924 }
925 else if (strcasecmp(cmd, "maint") == 0) {
926 as = cmd;
927 }
928 /* try to parse a weight here and keep the last one */
929 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
930 ps = cmd;
931 }
932 /* try to parse a maxconn here */
933 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
934 cs = cmd;
935 }
936 else {
937 /* keep a copy of the first error */
938 if (!err)
939 err = cmd;
940 }
941 /* skip to next word */
942 cmd = p;
943 }
944 /* here, cmd points either to \0 or to the beginning of a
945 * description. Skip possible leading spaces.
946 */
947 while (*cmd == ' ' || *cmd == '\n')
948 cmd++;
949
950 /* First, update the admin status so that we avoid sending other
951 * possibly useless warnings and can also update the health if
952 * present after going back up.
953 */
954 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200955 if (strcasecmp(as, "drain") == 0) {
956 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200957 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200958 }
959 else if (strcasecmp(as, "maint") == 0) {
960 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200961 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200962 }
963 else {
964 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200965 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200966 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200967 }
968
969 /* now change weights */
970 if (ps) {
971 const char *msg;
972
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500973 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200974 msg = server_parse_weight_change_request(check->server, ps);
975 if (!wrn || !*wrn)
976 wrn = msg;
977 }
978
979 if (cs) {
980 const char *msg;
981
982 cs += strlen("maxconn:");
983
Christopher Faulet147b8c92021-04-10 09:00:38 +0200984 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury Denoyelle02742862021-06-18 11:11:36 +0200985 /* This is safe to call server_parse_maxconn_change_request
986 * because the server lock is held during the check.
987 */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200988 msg = server_parse_maxconn_change_request(check->server, cs);
989 if (!wrn || !*wrn)
990 wrn = msg;
991 }
992
993 /* and finally health status */
994 if (hs) {
995 /* We'll report some of the warnings and errors we have
996 * here. Down reports are critical, we leave them untouched.
997 * Lack of report, or report of 'UP' leaves the room for
998 * ERR first, then WARN.
999 */
1000 const char *msg = cmd;
1001 struct buffer *t;
1002
1003 if (!*msg || status == HCHK_STATUS_L7OKD) {
1004 if (err && *err)
1005 msg = err;
1006 else if (wrn && *wrn)
1007 msg = wrn;
1008 }
1009
1010 t = get_trash_chunk();
1011 chunk_printf(t, "via agent : %s%s%s%s",
1012 hs, *msg ? " (" : "",
1013 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001014 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001015 set_server_check_status(check, status, t->area);
1016 }
1017 else if (err && *err) {
1018 /* No status change but we'd like to report something odd.
1019 * Just report the current state and copy the message.
1020 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001021 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001022 chunk_printf(&trash, "agent reports an error : %s", err);
1023 set_server_check_status(check, status/*check->status*/, trash.area);
1024 }
1025 else if (wrn && *wrn) {
1026 /* No status change but we'd like to report something odd.
1027 * Just report the current state and copy the message.
1028 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001029 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001030 chunk_printf(&trash, "agent warns : %s", wrn);
1031 set_server_check_status(check, status/*check->status*/, trash.area);
1032 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001033 else {
1034 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001035 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001036 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001037
1038 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001039 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001040 return ret;
1041
1042 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001043 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001044 ret = TCPCHK_EVAL_WAIT;
1045 goto out;
1046}
1047
1048/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1049 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1050 * TCPCHK_EVAL_STOP if an error occurred.
1051 */
1052enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1053{
1054 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1055 struct tcpcheck_connect *connect = &rule->connect;
1056 struct proxy *proxy = check->proxy;
1057 struct server *s = check->server;
1058 struct task *t = check->task;
Christopher 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
1193 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1194 flags |= CONNECT_HAS_DATA;
1195 if (!next || next->action != TCPCHK_ACT_EXPECT)
1196 flags |= CONNECT_DELACK_ALWAYS;
1197 status = proto->connect(conn, flags);
1198 }
1199
1200 if (status != SF_ERR_NONE)
1201 goto fail_check;
1202
Christopher Faulet21ddc742020-07-01 15:26:14 +02001203 conn_set_private(conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001204 conn->ctx = cs;
1205
Willy Tarreau51cd5952020-06-05 12:25:38 +02001206#ifdef USE_OPENSSL
1207 if (connect->sni)
1208 ssl_sock_set_servername(conn, connect->sni);
1209 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1210 ssl_sock_set_servername(conn, s->check.sni);
1211
1212 if (connect->alpn)
1213 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1214 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1215 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1216#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001217
1218 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1219 /* Some servers don't like reset on close */
Willy Tarreaub41a6e92021-04-06 17:49:19 +02001220 HA_ATOMIC_AND(&fdtab[cs->conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001221 }
1222
1223 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1224 if (xprt_add_hs(conn) < 0)
1225 status = SF_ERR_RESOURCE;
1226 }
1227
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001228 if (conn_xprt_start(conn) < 0) {
1229 status = SF_ERR_RESOURCE;
1230 goto fail_check;
1231 }
1232
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001233 /* The mux may be initialized now if there isn't server attached to the
1234 * check (email alerts) or if there is a mux proto specified or if there
1235 * is no alpn.
1236 */
1237 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1238 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1239 const struct mux_ops *mux_ops;
1240
Christopher Faulet147b8c92021-04-10 09:00:38 +02001241 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001242 if (connect->mux_proto)
1243 mux_ops = connect->mux_proto->mux;
1244 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1245 mux_ops = check->mux_proto->mux;
1246 else {
1247 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1248 ? PROTO_MODE_HTTP
1249 : PROTO_MODE_TCP);
1250
1251 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1252 }
1253 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001254 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001255 status = SF_ERR_INTERNAL;
1256 goto fail_check;
1257 }
1258 }
1259
Willy Tarreau51cd5952020-06-05 12:25:38 +02001260 fail_check:
1261 /* It can return one of :
1262 * - SF_ERR_NONE if everything's OK
1263 * - SF_ERR_SRVTO if there are no more servers
1264 * - SF_ERR_SRVCL if the connection was refused by the server
1265 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1266 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1267 * - SF_ERR_INTERNAL for any other purely internal errors
1268 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1269 * Note that we try to prevent the network stack from sending the ACK during the
1270 * connect() when a pure TCP check is used (without PROXY protocol).
1271 */
1272 switch (status) {
1273 case SF_ERR_NONE:
1274 /* we allow up to min(inter, timeout.connect) for a connection
1275 * to establish but only when timeout.check is set as it may be
1276 * to short for a full check otherwise
1277 */
1278 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1279
1280 if (proxy->timeout.check && proxy->timeout.connect) {
1281 int t_con = tick_add(now_ms, proxy->timeout.connect);
1282 t->expire = tick_first(t->expire, t_con);
1283 }
1284 break;
1285 case SF_ERR_SRVTO: /* ETIMEDOUT */
1286 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1287 case SF_ERR_PRXCOND:
1288 case SF_ERR_RESOURCE:
1289 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001290 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check, 0, 0, (size_t[]){status});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001291 chk_report_conn_err(check, errno, 0);
1292 ret = TCPCHK_EVAL_STOP;
1293 goto out;
1294 }
1295
1296 /* don't do anything until the connection is established */
1297 if (conn->flags & CO_FL_WAIT_XPRT) {
1298 if (conn->mux) {
1299 if (next && next->action == TCPCHK_ACT_SEND)
1300 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1301 else
1302 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
1303 }
1304 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001305 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001306 goto out;
1307 }
1308
1309 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001310 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001311 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001312 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001313 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001314
1315 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1316 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1317
Christopher Faulet147b8c92021-04-10 09:00:38 +02001318 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001319 return ret;
1320}
1321
1322/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1323 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1324 * TCPCHK_EVAL_STOP if an error occurred.
1325 */
1326enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1327{
1328 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1329 struct tcpcheck_send *send = &rule->send;
1330 struct conn_stream *cs = check->cs;
1331 struct connection *conn = cs_conn(cs);
1332 struct buffer *tmp = NULL;
1333 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001334 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001335
Christopher Faulet147b8c92021-04-10 09:00:38 +02001336 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1337
Christopher Fauletb381a502020-11-25 13:47:00 +01001338 if (check->state & CHK_ST_OUT_ALLOC) {
1339 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001340 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001341 goto out;
1342 }
1343
1344 if (!check_get_buf(check, &check->bo)) {
1345 check->state |= CHK_ST_OUT_ALLOC;
1346 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001347 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001348 goto out;
1349 }
1350
Christopher Faulet39066c22020-11-25 13:34:51 +01001351 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001352 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001353 TRACE_DEVEL("Data still pending, try to send it now", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Christopher Faulet39066c22020-11-25 13:34:51 +01001354 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001355 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001356
Christopher Fauletb381a502020-11-25 13:47:00 +01001357 /* Always release input buffer when a new send is evaluated */
1358 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001359
1360 switch (send->type) {
1361 case TCPCHK_SEND_STRING:
1362 case TCPCHK_SEND_BINARY:
1363 if (istlen(send->data) >= b_size(&check->bo)) {
1364 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1365 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1366 tcpcheck_get_step_id(check, rule));
1367 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1368 ret = TCPCHK_EVAL_STOP;
1369 goto out;
1370 }
1371 b_putist(&check->bo, send->data);
1372 break;
1373 case TCPCHK_SEND_STRING_LF:
1374 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1375 if (!b_data(&check->bo))
1376 goto out;
1377 break;
1378 case TCPCHK_SEND_BINARY_LF: {
1379 int len = b_size(&check->bo);
1380
1381 tmp = alloc_trash_chunk();
1382 if (!tmp)
1383 goto error_lf;
1384 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1385 if (!b_data(tmp))
1386 goto out;
1387 tmp->area[tmp->data] = '\0';
1388 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1389 goto error_lf;
1390 check->bo.data = len;
1391 break;
1392 }
1393 case TCPCHK_SEND_HTTP: {
1394 struct htx_sl *sl;
1395 struct ist meth, uri, vsn, clen, body;
1396 unsigned int slflags = 0;
1397
1398 tmp = alloc_trash_chunk();
1399 if (!tmp)
1400 goto error_htx;
1401
1402 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1403 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1404 : http_known_methods[send->http.meth.meth]);
1405 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1406 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1407 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1408 }
1409 else
1410 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1411 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1412
1413 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1414 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1415 slflags |= HTX_SL_F_VER_11;
1416 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1417 if (!isttest(send->http.body))
1418 slflags |= HTX_SL_F_BODYLESS;
1419
1420 htx = htx_from_buf(&check->bo);
1421 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1422 if (!sl)
1423 goto error_htx;
1424 sl->info.req.meth = send->http.meth.meth;
1425 if (!http_update_host(htx, sl, uri))
1426 goto error_htx;
1427
1428 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1429 struct tcpcheck_http_hdr *hdr;
1430 struct ist hdr_value;
1431
1432 list_for_each_entry(hdr, &send->http.hdrs, list) {
1433 chunk_reset(tmp);
1434 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1435 if (!b_data(tmp))
1436 continue;
1437 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1438 if (!htx_add_header(htx, hdr->name, hdr_value))
1439 goto error_htx;
1440 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1441 if (!http_update_authority(htx, sl, hdr_value))
1442 goto error_htx;
1443 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001444 if (isteqi(hdr->name, ist("connection")))
1445 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001446 }
1447
1448 }
1449 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1450 chunk_reset(tmp);
1451 httpchk_build_status_header(check->server, tmp);
1452 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1453 goto error_htx;
1454 }
1455
1456
1457 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1458 chunk_reset(tmp);
1459 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1460 body = ist2(b_orig(tmp), b_data(tmp));
1461 }
1462 else
1463 body = send->http.body;
1464 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1465
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001466 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001467 !htx_add_header(htx, ist("Content-length"), clen))
1468 goto error_htx;
1469
1470
1471 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001472 (istlen(body) && !htx_add_data_atonce(htx, body)))
1473 goto error_htx;
1474
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001475 /* no more data are expected */
1476 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001477 htx_to_buf(htx, &check->bo);
1478 break;
1479 }
1480 case TCPCHK_SEND_UNDEF:
1481 /* Should never happen. */
1482 ret = TCPCHK_EVAL_STOP;
1483 goto out;
1484 };
1485
Christopher Faulet39066c22020-11-25 13:34:51 +01001486 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001487 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001488 if (conn->mux->snd_buf(cs, &check->bo,
1489 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1490 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1491 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001492 TRACE_DEVEL("connection error during send", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001493 goto out;
1494 }
1495 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001496 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001497 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1498 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001499 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001500 goto out;
1501 }
1502
1503 out:
1504 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001505 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1506 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001507
1508 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001509 return ret;
1510
1511 error_htx:
1512 if (htx) {
1513 htx_reset(htx);
1514 htx_to_buf(htx, &check->bo);
1515 }
1516 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1517 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001518 TRACE_ERROR("failed to build HTTP request", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001519 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1520 ret = TCPCHK_EVAL_STOP;
1521 goto out;
1522
1523 error_lf:
1524 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1525 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001526 TRACE_ERROR("failed to build log-format string", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001527 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1528 ret = TCPCHK_EVAL_STOP;
1529 goto out;
1530
1531}
1532
1533/* Try to receive data before evaluating a tcp-check expect rule. Returns
1534 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1535 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1536 * TCPCHK_EVAL_STOP if an error occurred.
1537 */
1538enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1539{
1540 struct conn_stream *cs = check->cs;
1541 struct connection *conn = cs_conn(cs);
1542 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1543 size_t max, read, cur_read = 0;
1544 int is_empty;
1545 int read_poll = MAX_READ_POLL_LOOPS;
1546
Christopher Faulet147b8c92021-04-10 09:00:38 +02001547 TRACE_ENTER(CHK_EV_RX_DATA, check);
1548
1549 if (check->wait_list.events & SUB_RETRY_RECV) {
1550 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001551 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001552 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001553
1554 if (cs->flags & CS_FL_EOS)
1555 goto end_recv;
1556
Christopher Faulet147b8c92021-04-10 09:00:38 +02001557 if (check->state & CHK_ST_IN_ALLOC) {
1558 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001559 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001560 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001561
1562 if (!check_get_buf(check, &check->bi)) {
1563 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001564 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001565 goto wait_more_data;
1566 }
1567
Willy Tarreau51cd5952020-06-05 12:25:38 +02001568 /* errors on the connection and the conn-stream were already checked */
1569
1570 /* prepare to detect if the mux needs more room */
1571 cs->flags &= ~CS_FL_WANT_ROOM;
1572
1573 while ((cs->flags & CS_FL_RCV_MORE) ||
1574 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1575 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1576 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1577 cur_read += read;
1578 if (!read ||
1579 (cs->flags & CS_FL_WANT_ROOM) ||
1580 (--read_poll <= 0) ||
1581 (read < max && read >= global.tune.recv_enough))
1582 break;
1583 }
1584
1585 end_recv:
1586 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1587 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1588 /* Report network errors only if we got no other data. Otherwise
1589 * we'll let the upper layers decide whether the response is OK
1590 * or not. It is very common that an RST sent by the server is
1591 * reported as an error just after the last data chunk.
1592 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001593 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001594 goto stop;
1595 }
1596 if (!cur_read) {
1597 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1598 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001599 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001600 goto wait_more_data;
1601 }
1602 if (is_empty) {
1603 int status;
1604
1605 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1606 tcpcheck_get_step_id(check, rule));
1607 if (rule->comment)
1608 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1609
Christopher Faulet147b8c92021-04-10 09:00:38 +02001610 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001611 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1612 set_server_check_status(check, status, trash.area);
1613 goto stop;
1614 }
1615 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001616 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001617
1618 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001619 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1620 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001621
1622 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001623 return ret;
1624
1625 stop:
1626 ret = TCPCHK_EVAL_STOP;
1627 goto out;
1628
1629 wait_more_data:
1630 ret = TCPCHK_EVAL_WAIT;
1631 goto out;
1632}
1633
1634/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1635 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1636 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1637 * error occurred.
1638 */
1639enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1640{
1641 struct htx *htx = htxbuf(&check->bi);
1642 struct htx_sl *sl;
1643 struct htx_blk *blk;
1644 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1645 struct tcpcheck_expect *expect = &rule->expect;
1646 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1647 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1648 struct ist desc = IST_NULL;
1649 int i, match, inverse;
1650
Christopher Faulet147b8c92021-04-10 09:00:38 +02001651 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1652
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001653 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001654
1655 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001656 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001657 status = HCHK_STATUS_L7RSP;
1658 goto error;
1659 }
1660
1661 if (htx_is_empty(htx)) {
1662 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001663 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001664 status = HCHK_STATUS_L7RSP;
1665 goto error;
1666 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001667 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001668 goto wait_more_data;
1669 }
1670
1671 sl = http_get_stline(htx);
1672 check->code = sl->info.res.status;
1673
1674 if (check->server &&
1675 (check->server->proxy->options & PR_O_DISABLE404) &&
1676 (check->server->next_state != SRV_ST_STOPPED) &&
1677 (check->code == 404)) {
1678 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001679 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001680 goto out;
1681 }
1682
1683 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1684 /* Make GCC happy ; initialize match to a failure state. */
1685 match = inverse;
1686 status = expect->err_status;
1687
1688 switch (expect->type) {
1689 case TCPCHK_EXPECT_HTTP_STATUS:
1690 match = 0;
1691 for (i = 0; i < expect->codes.num; i++) {
1692 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1693 sl->info.res.status <= expect->codes.codes[i][1]) {
1694 match = 1;
1695 break;
1696 }
1697 }
1698
1699 /* Set status and description in case of error */
1700 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1701 if (LIST_ISEMPTY(&expect->onerror_fmt))
1702 desc = htx_sl_res_reason(sl);
1703 break;
1704 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1705 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1706
1707 /* Set status and description in case of error */
1708 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1709 if (LIST_ISEMPTY(&expect->onerror_fmt))
1710 desc = htx_sl_res_reason(sl);
1711 break;
1712
1713 case TCPCHK_EXPECT_HTTP_HEADER: {
1714 struct http_hdr_ctx ctx;
1715 struct ist npat, vpat, value;
1716 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1717
1718 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1719 nbuf = alloc_trash_chunk();
1720 if (!nbuf) {
1721 status = HCHK_STATUS_L7RSP;
1722 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001723 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001724 goto error;
1725 }
1726 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1727 if (!b_data(nbuf)) {
1728 status = HCHK_STATUS_L7RSP;
1729 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001730 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001731 goto error;
1732 }
1733 npat = ist2(b_orig(nbuf), b_data(nbuf));
1734 }
1735 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1736 npat = expect->hdr.name;
1737
1738 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1739 vbuf = alloc_trash_chunk();
1740 if (!vbuf) {
1741 status = HCHK_STATUS_L7RSP;
1742 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001743 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001744 goto error;
1745 }
1746 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1747 if (!b_data(vbuf)) {
1748 status = HCHK_STATUS_L7RSP;
1749 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001750 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001751 goto error;
1752 }
1753 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1754 }
1755 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1756 vpat = expect->hdr.value;
1757
1758 match = 0;
1759 ctx.blk = NULL;
1760 while (1) {
1761 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1762 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1763 if (!http_find_str_header(htx, npat, &ctx, full))
1764 goto end_of_match;
1765 break;
1766 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1767 if (!http_find_pfx_header(htx, npat, &ctx, full))
1768 goto end_of_match;
1769 break;
1770 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1771 if (!http_find_sfx_header(htx, npat, &ctx, full))
1772 goto end_of_match;
1773 break;
1774 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1775 if (!http_find_sub_header(htx, npat, &ctx, full))
1776 goto end_of_match;
1777 break;
1778 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1779 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1780 goto end_of_match;
1781 break;
1782 default:
1783 /* should never happen */
1784 goto end_of_match;
1785 }
1786
1787 /* A header has matched the name pattern, let's test its
1788 * value now (always defined from there). If there is no
1789 * value pattern, it is a good match.
1790 */
1791
1792 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1793 match = 1;
1794 goto end_of_match;
1795 }
1796
1797 value = ctx.value;
1798 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1799 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1800 if (isteq(value, vpat)) {
1801 match = 1;
1802 goto end_of_match;
1803 }
1804 break;
1805 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1806 if (istlen(value) < istlen(vpat))
1807 break;
1808 value = ist2(istptr(value), istlen(vpat));
1809 if (isteq(value, vpat)) {
1810 match = 1;
1811 goto end_of_match;
1812 }
1813 break;
1814 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1815 if (istlen(value) < istlen(vpat))
1816 break;
1817 value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
1818 if (isteq(value, vpat)) {
1819 match = 1;
1820 goto end_of_match;
1821 }
1822 break;
1823 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1824 if (isttest(istist(value, vpat))) {
1825 match = 1;
1826 goto end_of_match;
1827 }
1828 break;
1829 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1830 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1831 match = 1;
1832 goto end_of_match;
1833 }
1834 break;
1835 }
1836 }
1837
1838 end_of_match:
1839 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1840 if (LIST_ISEMPTY(&expect->onerror_fmt))
1841 desc = htx_sl_res_reason(sl);
1842 break;
1843 }
1844
1845 case TCPCHK_EXPECT_HTTP_BODY:
1846 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1847 case TCPCHK_EXPECT_HTTP_BODY_LF:
1848 match = 0;
1849 chunk_reset(&trash);
1850 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1851 enum htx_blk_type type = htx_get_blk_type(blk);
1852
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001853 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001854 break;
1855 if (type == HTX_BLK_DATA) {
1856 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1857 break;
1858 }
1859 }
1860
1861 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001862 if (!last_read) {
1863 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001864 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001865 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001866 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1867 if (LIST_ISEMPTY(&expect->onerror_fmt))
1868 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001869 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001870 goto error;
1871 }
1872
1873 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1874 tmp = alloc_trash_chunk();
1875 if (!tmp) {
1876 status = HCHK_STATUS_L7RSP;
1877 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001878 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001879 goto error;
1880 }
1881 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1882 if (!b_data(tmp)) {
1883 status = HCHK_STATUS_L7RSP;
1884 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001885 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001886 goto error;
1887 }
1888 }
1889
1890 if (!last_read &&
1891 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1892 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1893 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1894 ret = TCPCHK_EVAL_WAIT;
1895 goto out;
1896 }
1897
1898 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1899 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1900 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1901 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1902 else
1903 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1904
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001905 /* Wait for more data on mismatch only if no minimum is defined (-1),
1906 * otherwise the absence of match is already conclusive.
1907 */
1908 if (!match && !last_read && (expect->min_recv == -1)) {
1909 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001910 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001911 goto out;
1912 }
1913
Willy Tarreau51cd5952020-06-05 12:25:38 +02001914 /* Set status and description in case of error */
1915 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1916 if (LIST_ISEMPTY(&expect->onerror_fmt))
1917 desc = (inverse
1918 ? ist("HTTP check matched unwanted content")
1919 : ist("HTTP content check did not match"));
1920 break;
1921
1922
1923 default:
1924 /* should never happen */
1925 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1926 goto error;
1927 }
1928
Christopher Faulet147b8c92021-04-10 09:00:38 +02001929 if (!(match ^ inverse)) {
1930 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001931 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001932 }
1933
1934 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001935
1936 out:
1937 free_trash_chunk(tmp);
1938 free_trash_chunk(nbuf);
1939 free_trash_chunk(vbuf);
1940 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001941 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001942 return ret;
1943
1944 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001945 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001946 ret = TCPCHK_EVAL_STOP;
1947 msg = alloc_trash_chunk();
1948 if (msg)
1949 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1950 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1951 goto out;
1952
1953 wait_more_data:
1954 ret = TCPCHK_EVAL_WAIT;
1955 goto out;
1956}
1957
1958/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1959 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1960 * if an error occurred.
1961 */
1962enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1963{
1964 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1965 struct tcpcheck_expect *expect = &rule->expect;
1966 struct buffer *msg = NULL, *tmp = NULL;
1967 struct ist desc = IST_NULL;
1968 enum healthcheck_status status;
1969 int match, inverse;
1970
Christopher Faulet147b8c92021-04-10 09:00:38 +02001971 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1972
Willy Tarreau51cd5952020-06-05 12:25:38 +02001973 last_read |= b_full(&check->bi);
1974
1975 /* The current expect might need more data than the previous one, check again
1976 * that the minimum amount data required to match is respected.
1977 */
1978 if (!last_read) {
1979 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1980 (b_data(&check->bi) < istlen(expect->data))) {
1981 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001982 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001983 goto out;
1984 }
1985 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1986 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001987 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001988 goto out;
1989 }
1990 }
1991
1992 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1993 /* Make GCC happy ; initialize match to a failure state. */
1994 match = inverse;
1995 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1996
1997 switch (expect->type) {
1998 case TCPCHK_EXPECT_STRING:
1999 case TCPCHK_EXPECT_BINARY:
2000 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2001 break;
2002 case TCPCHK_EXPECT_STRING_REGEX:
2003 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2004 break;
2005
2006 case TCPCHK_EXPECT_BINARY_REGEX:
2007 chunk_reset(&trash);
2008 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2009 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2010 break;
2011
2012 case TCPCHK_EXPECT_STRING_LF:
2013 case TCPCHK_EXPECT_BINARY_LF:
2014 match = 0;
2015 tmp = alloc_trash_chunk();
2016 if (!tmp) {
2017 status = HCHK_STATUS_L7RSP;
2018 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002019 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002020 goto error;
2021 }
2022 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2023 if (!b_data(tmp)) {
2024 status = HCHK_STATUS_L7RSP;
2025 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002026 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002027 goto error;
2028 }
2029 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2030 int len = tmp->data;
2031 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2032 status = HCHK_STATUS_L7RSP;
2033 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002034 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002035 goto error;
2036 }
2037 tmp->data = len;
2038 }
2039 if (b_data(&check->bi) < tmp->data) {
2040 if (!last_read) {
2041 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002042 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002043 goto out;
2044 }
2045 break;
2046 }
2047 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2048 break;
2049
2050 case TCPCHK_EXPECT_CUSTOM:
2051 if (expect->custom)
2052 ret = expect->custom(check, rule, last_read);
2053 goto out;
2054 default:
2055 /* Should never happen. */
2056 ret = TCPCHK_EVAL_STOP;
2057 goto out;
2058 }
2059
2060
2061 /* Wait for more data on mismatch only if no minimum is defined (-1),
2062 * otherwise the absence of match is already conclusive.
2063 */
2064 if (!match && !last_read && (expect->min_recv == -1)) {
2065 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002066 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002067 goto out;
2068 }
2069
2070 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002071 if (match ^ inverse) {
2072 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002073 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002074 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002075
2076 error:
2077 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002078 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002079 ret = TCPCHK_EVAL_STOP;
2080 msg = alloc_trash_chunk();
2081 if (msg)
2082 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2083 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2084 free_trash_chunk(msg);
2085
2086 out:
2087 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002088 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002089 return ret;
2090}
2091
2092/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2093 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2094 * waits.
2095 */
2096enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2097{
2098 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2099 struct act_rule *act_rule;
2100 enum act_return act_ret;
2101
2102 act_rule =rule->action_kw.rule;
2103 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2104 if (act_ret != ACT_RET_CONT) {
2105 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2106 tcpcheck_get_step_id(check, rule));
2107 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2108 ret = TCPCHK_EVAL_STOP;
2109 }
2110
2111 return ret;
2112}
2113
2114/* Executes a tcp-check ruleset. Note that this is called both from the
2115 * connection's wake() callback and from the check scheduling task. It returns
2116 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2117 * presenting the risk of an fd replacement.
2118 *
2119 * Please do NOT place any return statement in this function and only leave
2120 * via the out_end_tcpcheck label after setting retcode.
2121 */
2122int tcpcheck_main(struct check *check)
2123{
2124 struct tcpcheck_rule *rule;
2125 struct conn_stream *cs = check->cs;
2126 struct connection *conn = cs_conn(cs);
2127 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002128 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002129 enum tcpcheck_eval_ret eval_ret;
2130
2131 /* here, we know that the check is complete or that it failed */
2132 if (check->result != CHK_RES_UNKNOWN)
2133 goto out;
2134
Christopher Faulet147b8c92021-04-10 09:00:38 +02002135 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2136
Willy Tarreau51cd5952020-06-05 12:25:38 +02002137 /* Note: the conn-stream and the connection may only be undefined before
2138 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002139 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002140 */
2141
2142 /* 1- check for connection error, if any */
2143 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2144 goto out_end_tcpcheck;
2145
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002146 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002147 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002148 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002149 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002150 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2151 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002152
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002153 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002154 * tcp-check variables */
2155 else {
2156 struct tcpcheck_var *var;
2157
2158 /* First evaluation, create a session */
2159 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2160 if (!check->sess) {
2161 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002162 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002163 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2164 goto out_end_tcpcheck;
2165 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002166 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002167 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2168
2169 /* Preset tcp-check variables */
2170 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2171 struct sample smp;
2172
2173 memset(&smp, 0, sizeof(smp));
2174 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2175 smp.data = var->data;
2176 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2177 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002178 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002179 }
2180
2181 /* Now evaluate the tcp-check rules */
2182
2183 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2184 check->code = 0;
2185 switch (rule->action) {
2186 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002187 /* Not the first connection, release it first */
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002188 if (cs && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002189 check->state |= CHK_ST_CLOSE_CONN;
2190 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002191 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002192
2193 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002194
2195 /* We are still waiting the connection gets closed */
2196 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002197 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002198 eval_ret = TCPCHK_EVAL_WAIT;
2199 break;
2200 }
2201
Christopher Faulet147b8c92021-04-10 09:00:38 +02002202 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002203 eval_ret = tcpcheck_eval_connect(check, rule);
2204
2205 /* Refresh conn-stream and connection */
2206 cs = check->cs;
2207 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002208 last_read = 0;
2209 must_read = ((cs && IS_HTX_CS(cs)) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002210 break;
2211 case TCPCHK_ACT_SEND:
2212 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002213 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002214 eval_ret = tcpcheck_eval_send(check, rule);
2215 must_read = 1;
2216 break;
2217 case TCPCHK_ACT_EXPECT:
2218 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002219 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002220 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002221 eval_ret = tcpcheck_eval_recv(check, rule);
2222 if (eval_ret == TCPCHK_EVAL_STOP)
2223 goto out_end_tcpcheck;
2224 else if (eval_ret == TCPCHK_EVAL_WAIT)
2225 goto out;
2226 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2227 must_read = 0;
2228 }
2229
2230 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2231 ? tcpcheck_eval_expect_http(check, rule, last_read)
2232 : tcpcheck_eval_expect(check, rule, last_read));
2233
2234 if (eval_ret == TCPCHK_EVAL_WAIT) {
2235 check->current_step = rule->expect.head;
2236 if (!(check->wait_list.events & SUB_RETRY_RECV))
2237 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2238 }
2239 break;
2240 case TCPCHK_ACT_ACTION_KW:
2241 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002242 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002243 eval_ret = tcpcheck_eval_action_kw(check, rule);
2244 break;
2245 default:
2246 /* Otherwise, just go to the next one and don't update
2247 * the current step
2248 */
2249 eval_ret = TCPCHK_EVAL_CONTINUE;
2250 break;
2251 }
2252
2253 switch (eval_ret) {
2254 case TCPCHK_EVAL_CONTINUE:
2255 break;
2256 case TCPCHK_EVAL_WAIT:
2257 goto out;
2258 case TCPCHK_EVAL_STOP:
2259 goto out_end_tcpcheck;
2260 }
2261 }
2262
2263 /* All rules was evaluated */
2264 if (check->current_step) {
2265 rule = check->current_step;
2266
Christopher Faulet147b8c92021-04-10 09:00:38 +02002267 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2268
Willy Tarreau51cd5952020-06-05 12:25:38 +02002269 if (rule->action == TCPCHK_ACT_EXPECT) {
2270 struct buffer *msg;
2271 enum healthcheck_status status;
2272
2273 if (check->server &&
2274 (check->server->proxy->options & PR_O_DISABLE404) &&
2275 (check->server->next_state != SRV_ST_STOPPED) &&
2276 (check->code == 404)) {
2277 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002278 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002279 goto out_end_tcpcheck;
2280 }
2281
2282 msg = alloc_trash_chunk();
2283 if (msg)
2284 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2285 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2286 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2287 free_trash_chunk(msg);
2288 }
2289 else if (rule->action == TCPCHK_ACT_CONNECT) {
2290 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2291 enum healthcheck_status status = HCHK_STATUS_L4OK;
2292#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002293 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002294 status = HCHK_STATUS_L6OK;
2295#endif
2296 set_server_check_status(check, status, msg);
2297 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002298 else
2299 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002300 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002301 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002302 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002303 }
2304 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002305
2306 out_end_tcpcheck:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002307 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) {
2308 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002309 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002310 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002311
Christopher Fauletb381a502020-11-25 13:47:00 +01002312 /* the tcpcheck is finished, release in/out buffer now */
2313 check_release_buf(check, &check->bi);
2314 check_release_buf(check, &check->bo);
2315
Willy Tarreau51cd5952020-06-05 12:25:38 +02002316 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002317 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002318 return retcode;
2319}
2320
2321
2322/**************************************************************************/
2323/******************* Internals to parse tcp-check rules *******************/
2324/**************************************************************************/
2325struct action_kw_list tcp_check_keywords = {
2326 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2327};
2328
2329/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2330 * returned on error.
2331 */
2332struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2333 struct list *rules, struct action_kw *kw,
2334 const char *file, int line, char **errmsg)
2335{
2336 struct tcpcheck_rule *chk = NULL;
2337 struct act_rule *actrule = NULL;
2338
Willy Tarreaud535f802021-10-11 08:49:26 +02002339 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002340 if (!actrule) {
2341 memprintf(errmsg, "out of memory");
2342 goto error;
2343 }
2344 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002345
2346 cur_arg++;
2347 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2348 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2349 goto error;
2350 }
2351
2352 chk = calloc(1, sizeof(*chk));
2353 if (!chk) {
2354 memprintf(errmsg, "out of memory");
2355 goto error;
2356 }
2357 chk->action = TCPCHK_ACT_ACTION_KW;
2358 chk->action_kw.rule = actrule;
2359 return chk;
2360
2361 error:
2362 free(actrule);
2363 return NULL;
2364}
2365
2366/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2367 * returned on error.
2368 */
2369struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2370 const char *file, int line, char **errmsg)
2371{
2372 struct tcpcheck_rule *chk = NULL;
2373 struct sockaddr_storage *sk = NULL;
2374 char *comment = NULL, *sni = NULL, *alpn = NULL;
2375 struct sample_expr *port_expr = NULL;
2376 const struct mux_proto_list *mux_proto = NULL;
2377 unsigned short conn_opts = 0;
2378 long port = 0;
2379 int alpn_len = 0;
2380
2381 list_for_each_entry(chk, rules, list) {
2382 if (chk->action == TCPCHK_ACT_CONNECT)
2383 break;
2384 if (chk->action == TCPCHK_ACT_COMMENT ||
2385 chk->action == TCPCHK_ACT_ACTION_KW ||
2386 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2387 continue;
2388
2389 memprintf(errmsg, "first step MUST also be a 'connect', "
2390 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2391 "when there is a 'connect' step in the tcp-check ruleset");
2392 goto error;
2393 }
2394
2395 cur_arg++;
2396 while (*(args[cur_arg])) {
2397 if (strcmp(args[cur_arg], "default") == 0)
2398 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2399 else if (strcmp(args[cur_arg], "addr") == 0) {
2400 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002401
2402 if (!*(args[cur_arg+1])) {
2403 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2404 goto error;
2405 }
2406
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002407 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2408 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002409 if (!sk) {
2410 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2411 goto error;
2412 }
2413
Willy Tarreau51cd5952020-06-05 12:25:38 +02002414 cur_arg++;
2415 }
2416 else if (strcmp(args[cur_arg], "port") == 0) {
2417 const char *p, *end;
2418
2419 if (!*(args[cur_arg+1])) {
2420 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2421 goto error;
2422 }
2423 cur_arg++;
2424
2425 port = 0;
2426 release_sample_expr(port_expr);
2427 p = args[cur_arg]; end = p + strlen(p);
2428 port = read_uint(&p, end);
2429 if (p != end) {
2430 int idx = 0;
2431
2432 px->conf.args.ctx = ARGC_SRV;
2433 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002434 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002435
2436 if (!port_expr) {
2437 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2438 goto error;
2439 }
2440 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2441 memprintf(errmsg, "error detected while parsing port expression : "
2442 " fetch method '%s' extracts information from '%s', "
2443 "none of which is available here.\n",
2444 args[cur_arg], sample_src_names(port_expr->fetch->use));
2445 goto error;
2446 }
2447 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2448 }
2449 else if (port > 65535 || port < 1) {
2450 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2451 args[cur_arg]);
2452 goto error;
2453 }
2454 }
2455 else if (strcmp(args[cur_arg], "proto") == 0) {
2456 if (!*(args[cur_arg+1])) {
2457 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2458 goto error;
2459 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002460 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002461 if (!mux_proto) {
2462 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2463 goto error;
2464 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002465
2466 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2467 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2468 goto error;
2469 }
2470 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2471 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2472 goto error;
2473 }
2474
Willy Tarreau51cd5952020-06-05 12:25:38 +02002475 cur_arg++;
2476 }
2477 else if (strcmp(args[cur_arg], "comment") == 0) {
2478 if (!*(args[cur_arg+1])) {
2479 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2480 goto error;
2481 }
2482 cur_arg++;
2483 free(comment);
2484 comment = strdup(args[cur_arg]);
2485 if (!comment) {
2486 memprintf(errmsg, "out of memory");
2487 goto error;
2488 }
2489 }
2490 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2491 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2492 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2493 conn_opts |= TCPCHK_OPT_SOCKS4;
2494 else if (strcmp(args[cur_arg], "linger") == 0)
2495 conn_opts |= TCPCHK_OPT_LINGER;
2496#ifdef USE_OPENSSL
2497 else if (strcmp(args[cur_arg], "ssl") == 0) {
2498 px->options |= PR_O_TCPCHK_SSL;
2499 conn_opts |= TCPCHK_OPT_SSL;
2500 }
2501 else if (strcmp(args[cur_arg], "sni") == 0) {
2502 if (!*(args[cur_arg+1])) {
2503 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2504 goto error;
2505 }
2506 cur_arg++;
2507 free(sni);
2508 sni = strdup(args[cur_arg]);
2509 if (!sni) {
2510 memprintf(errmsg, "out of memory");
2511 goto error;
2512 }
2513 }
2514 else if (strcmp(args[cur_arg], "alpn") == 0) {
2515#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2516 free(alpn);
2517 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2518 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2519 goto error;
2520 }
2521 cur_arg++;
2522#else
2523 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2524 goto error;
2525#endif
2526 }
2527#endif /* USE_OPENSSL */
2528
2529 else {
2530 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2531#ifdef USE_OPENSSL
2532 ", 'ssl', 'sni', 'alpn'"
2533#endif /* USE_OPENSSL */
2534 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2535 args[cur_arg]);
2536 goto error;
2537 }
2538 cur_arg++;
2539 }
2540
2541 chk = calloc(1, sizeof(*chk));
2542 if (!chk) {
2543 memprintf(errmsg, "out of memory");
2544 goto error;
2545 }
2546 chk->action = TCPCHK_ACT_CONNECT;
2547 chk->comment = comment;
2548 chk->connect.port = port;
2549 chk->connect.options = conn_opts;
2550 chk->connect.sni = sni;
2551 chk->connect.alpn = alpn;
2552 chk->connect.alpn_len= alpn_len;
2553 chk->connect.port_expr= port_expr;
2554 chk->connect.mux_proto= mux_proto;
2555 if (sk)
2556 chk->connect.addr = *sk;
2557 return chk;
2558
2559 error:
2560 free(alpn);
2561 free(sni);
2562 free(comment);
2563 release_sample_expr(port_expr);
2564 return NULL;
2565}
2566
2567/* Parses and creates a tcp-check send rule. NULL is returned on error */
2568struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2569 const char *file, int line, char **errmsg)
2570{
2571 struct tcpcheck_rule *chk = NULL;
2572 char *comment = NULL, *data = NULL;
2573 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2574
2575 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2576 type = TCPCHK_SEND_BINARY_LF;
2577 else if (strcmp(args[cur_arg], "send-binary") == 0)
2578 type = TCPCHK_SEND_BINARY;
2579 else if (strcmp(args[cur_arg], "send-lf") == 0)
2580 type = TCPCHK_SEND_STRING_LF;
2581 else if (strcmp(args[cur_arg], "send") == 0)
2582 type = TCPCHK_SEND_STRING;
2583
2584 if (!*(args[cur_arg+1])) {
2585 memprintf(errmsg, "'%s' expects a %s as argument",
2586 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2587 goto error;
2588 }
2589
2590 data = args[cur_arg+1];
2591
2592 cur_arg += 2;
2593 while (*(args[cur_arg])) {
2594 if (strcmp(args[cur_arg], "comment") == 0) {
2595 if (!*(args[cur_arg+1])) {
2596 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2597 goto error;
2598 }
2599 cur_arg++;
2600 free(comment);
2601 comment = strdup(args[cur_arg]);
2602 if (!comment) {
2603 memprintf(errmsg, "out of memory");
2604 goto error;
2605 }
2606 }
2607 else {
2608 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2609 args[cur_arg]);
2610 goto error;
2611 }
2612 cur_arg++;
2613 }
2614
2615 chk = calloc(1, sizeof(*chk));
2616 if (!chk) {
2617 memprintf(errmsg, "out of memory");
2618 goto error;
2619 }
2620 chk->action = TCPCHK_ACT_SEND;
2621 chk->comment = comment;
2622 chk->send.type = type;
2623
2624 switch (chk->send.type) {
2625 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002626 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002627 if (!isttest(chk->send.data)) {
2628 memprintf(errmsg, "out of memory");
2629 goto error;
2630 }
2631 break;
2632 case TCPCHK_SEND_BINARY: {
2633 int len = chk->send.data.len;
2634 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2635 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2636 goto error;
2637 }
2638 chk->send.data.len = len;
2639 break;
2640 }
2641 case TCPCHK_SEND_STRING_LF:
2642 case TCPCHK_SEND_BINARY_LF:
2643 LIST_INIT(&chk->send.fmt);
2644 px->conf.args.ctx = ARGC_SRV;
2645 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2646 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2647 goto error;
2648 }
2649 break;
2650 case TCPCHK_SEND_HTTP:
2651 case TCPCHK_SEND_UNDEF:
2652 goto error;
2653 }
2654
2655 return chk;
2656
2657 error:
2658 free(chk);
2659 free(comment);
2660 return NULL;
2661}
2662
2663/* Parses and creates a http-check send rule. NULL is returned on error */
2664struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2665 const char *file, int line, char **errmsg)
2666{
2667 struct tcpcheck_rule *chk = NULL;
2668 struct tcpcheck_http_hdr *hdr = NULL;
2669 struct http_hdr hdrs[global.tune.max_http_hdr];
2670 char *meth = NULL, *uri = NULL, *vsn = NULL;
2671 char *body = NULL, *comment = NULL;
2672 unsigned int flags = 0;
2673 int i = 0, host_hdr = -1;
2674
2675 cur_arg++;
2676 while (*(args[cur_arg])) {
2677 if (strcmp(args[cur_arg], "meth") == 0) {
2678 if (!*(args[cur_arg+1])) {
2679 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2680 goto error;
2681 }
2682 cur_arg++;
2683 meth = args[cur_arg];
2684 }
2685 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2686 if (!*(args[cur_arg+1])) {
2687 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2688 goto error;
2689 }
2690 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2691 if (strcmp(args[cur_arg], "uri-lf") == 0)
2692 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2693 cur_arg++;
2694 uri = args[cur_arg];
2695 }
2696 else if (strcmp(args[cur_arg], "ver") == 0) {
2697 if (!*(args[cur_arg+1])) {
2698 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2699 goto error;
2700 }
2701 cur_arg++;
2702 vsn = args[cur_arg];
2703 }
2704 else if (strcmp(args[cur_arg], "hdr") == 0) {
2705 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2706 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2707 goto error;
2708 }
2709
2710 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2711 if (host_hdr >= 0) {
2712 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2713 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2714 goto error;
2715 }
2716 host_hdr = i;
2717 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002718 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002719 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2720 goto skip_hdr;
2721
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002722 hdrs[i].n = ist(args[cur_arg + 1]);
2723 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002724 i++;
2725 skip_hdr:
2726 cur_arg += 2;
2727 }
2728 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2729 if (!*(args[cur_arg+1])) {
2730 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2731 goto error;
2732 }
2733 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2734 if (strcmp(args[cur_arg], "body-lf") == 0)
2735 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2736 cur_arg++;
2737 body = args[cur_arg];
2738 }
2739 else if (strcmp(args[cur_arg], "comment") == 0) {
2740 if (!*(args[cur_arg+1])) {
2741 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2742 goto error;
2743 }
2744 cur_arg++;
2745 free(comment);
2746 comment = strdup(args[cur_arg]);
2747 if (!comment) {
2748 memprintf(errmsg, "out of memory");
2749 goto error;
2750 }
2751 }
2752 else {
2753 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2754 " but got '%s' as argument.", args[cur_arg]);
2755 goto error;
2756 }
2757 cur_arg++;
2758 }
2759
2760 hdrs[i].n = hdrs[i].v = IST_NULL;
2761
2762 chk = calloc(1, sizeof(*chk));
2763 if (!chk) {
2764 memprintf(errmsg, "out of memory");
2765 goto error;
2766 }
2767 chk->action = TCPCHK_ACT_SEND;
2768 chk->comment = comment; comment = NULL;
2769 chk->send.type = TCPCHK_SEND_HTTP;
2770 chk->send.http.flags = flags;
2771 LIST_INIT(&chk->send.http.hdrs);
2772
2773 if (meth) {
2774 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2775 chk->send.http.meth.str.area = strdup(meth);
2776 chk->send.http.meth.str.data = strlen(meth);
2777 if (!chk->send.http.meth.str.area) {
2778 memprintf(errmsg, "out of memory");
2779 goto error;
2780 }
2781 }
2782 if (uri) {
2783 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2784 LIST_INIT(&chk->send.http.uri_fmt);
2785 px->conf.args.ctx = ARGC_SRV;
2786 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2787 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2788 goto error;
2789 }
2790 }
2791 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002792 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002793 if (!isttest(chk->send.http.uri)) {
2794 memprintf(errmsg, "out of memory");
2795 goto error;
2796 }
2797 }
2798 }
2799 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002800 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002801 if (!isttest(chk->send.http.vsn)) {
2802 memprintf(errmsg, "out of memory");
2803 goto error;
2804 }
2805 }
2806 for (i = 0; istlen(hdrs[i].n); i++) {
2807 hdr = calloc(1, sizeof(*hdr));
2808 if (!hdr) {
2809 memprintf(errmsg, "out of memory");
2810 goto error;
2811 }
2812 LIST_INIT(&hdr->value);
2813 hdr->name = istdup(hdrs[i].n);
2814 if (!isttest(hdr->name)) {
2815 memprintf(errmsg, "out of memory");
2816 goto error;
2817 }
2818
2819 ist0(hdrs[i].v);
2820 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2821 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002822 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002823 hdr = NULL;
2824 }
2825
2826 if (body) {
2827 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2828 LIST_INIT(&chk->send.http.body_fmt);
2829 px->conf.args.ctx = ARGC_SRV;
2830 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2831 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2832 goto error;
2833 }
2834 }
2835 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002836 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002837 if (!isttest(chk->send.http.body)) {
2838 memprintf(errmsg, "out of memory");
2839 goto error;
2840 }
2841 }
2842 }
2843
2844 return chk;
2845
2846 error:
2847 free_tcpcheck_http_hdr(hdr);
2848 free_tcpcheck(chk, 0);
2849 free(comment);
2850 return NULL;
2851}
2852
2853/* Parses and creates a http-check comment rule. NULL is returned on error */
2854struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2855 const char *file, int line, char **errmsg)
2856{
2857 struct tcpcheck_rule *chk = NULL;
2858 char *comment = NULL;
2859
2860 if (!*(args[cur_arg+1])) {
2861 memprintf(errmsg, "expects a string as argument");
2862 goto error;
2863 }
2864 cur_arg++;
2865 comment = strdup(args[cur_arg]);
2866 if (!comment) {
2867 memprintf(errmsg, "out of memory");
2868 goto error;
2869 }
2870
2871 chk = calloc(1, sizeof(*chk));
2872 if (!chk) {
2873 memprintf(errmsg, "out of memory");
2874 goto error;
2875 }
2876 chk->action = TCPCHK_ACT_COMMENT;
2877 chk->comment = comment;
2878 return chk;
2879
2880 error:
2881 free(comment);
2882 return NULL;
2883}
2884
2885/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2886 * on error. <proto> is set to the right protocol flags (covered by the
2887 * TCPCHK_RULES_PROTO_CHK mask).
2888 */
2889struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2890 struct list *rules, unsigned int proto,
2891 const char *file, int line, char **errmsg)
2892{
2893 struct tcpcheck_rule *prev_check, *chk = NULL;
2894 struct sample_expr *status_expr = NULL;
2895 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2896 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2897 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2898 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2899 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2900 unsigned int flags = 0;
2901 long min_recv = -1;
2902 int inverse = 0;
2903
2904 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2905 if (!*(args[cur_arg+1])) {
2906 memprintf(errmsg, "expects at least a matching pattern as arguments");
2907 goto error;
2908 }
2909
2910 cur_arg++;
2911 while (*(args[cur_arg])) {
2912 int in_pattern = 0;
2913
2914 rescan:
2915 if (strcmp(args[cur_arg], "min-recv") == 0) {
2916 if (in_pattern) {
2917 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2918 goto error;
2919 }
2920 if (!*(args[cur_arg+1])) {
2921 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2922 goto error;
2923 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002924 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002925 cur_arg++;
2926 min_recv = atol(args[cur_arg]);
2927 if (min_recv < -1 || min_recv > INT_MAX) {
2928 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2929 goto error;
2930 }
2931 }
2932 else if (*(args[cur_arg]) == '!') {
2933 in_pattern = 1;
2934 while (*(args[cur_arg]) == '!') {
2935 inverse = !inverse;
2936 args[cur_arg]++;
2937 }
2938 if (!*(args[cur_arg]))
2939 cur_arg++;
2940 goto rescan;
2941 }
2942 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2943 if (type != TCPCHK_EXPECT_UNDEF) {
2944 memprintf(errmsg, "only on pattern expected");
2945 goto error;
2946 }
2947 if (proto != TCPCHK_RULES_HTTP_CHK)
2948 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2949 else
2950 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2951
2952 if (!*(args[cur_arg+1])) {
2953 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2954 goto error;
2955 }
2956 cur_arg++;
2957 pattern = args[cur_arg];
2958 }
2959 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2960 if (proto == TCPCHK_RULES_HTTP_CHK)
2961 goto bad_http_kw;
2962 if (type != TCPCHK_EXPECT_UNDEF) {
2963 memprintf(errmsg, "only on pattern expected");
2964 goto error;
2965 }
2966 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2967
2968 if (!*(args[cur_arg+1])) {
2969 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2970 goto error;
2971 }
2972 cur_arg++;
2973 pattern = args[cur_arg];
2974 }
2975 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2976 if (type != TCPCHK_EXPECT_UNDEF) {
2977 memprintf(errmsg, "only on pattern expected");
2978 goto error;
2979 }
2980 if (proto != TCPCHK_RULES_HTTP_CHK)
2981 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2982 else {
2983 if (*(args[cur_arg]) != 's')
2984 goto bad_http_kw;
2985 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2986 }
2987
2988 if (!*(args[cur_arg+1])) {
2989 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2990 goto error;
2991 }
2992 cur_arg++;
2993 pattern = args[cur_arg];
2994 }
2995 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
2996 if (proto != TCPCHK_RULES_HTTP_CHK)
2997 goto bad_tcp_kw;
2998 if (type != TCPCHK_EXPECT_UNDEF) {
2999 memprintf(errmsg, "only on pattern expected");
3000 goto error;
3001 }
3002 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3003
3004 if (!*(args[cur_arg+1])) {
3005 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3006 goto error;
3007 }
3008 cur_arg++;
3009 pattern = args[cur_arg];
3010 }
3011 else if (strcmp(args[cur_arg], "custom") == 0) {
3012 if (in_pattern) {
3013 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3014 goto error;
3015 }
3016 if (type != TCPCHK_EXPECT_UNDEF) {
3017 memprintf(errmsg, "only on pattern expected");
3018 goto error;
3019 }
3020 type = TCPCHK_EXPECT_CUSTOM;
3021 }
3022 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3023 int orig_arg = cur_arg;
3024
3025 if (proto != TCPCHK_RULES_HTTP_CHK)
3026 goto bad_tcp_kw;
3027 if (type != TCPCHK_EXPECT_UNDEF) {
3028 memprintf(errmsg, "only on pattern expected");
3029 goto error;
3030 }
3031 type = TCPCHK_EXPECT_HTTP_HEADER;
3032
3033 if (strcmp(args[cur_arg], "fhdr") == 0)
3034 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3035
3036 /* Parse the name pattern, mandatory */
3037 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3038 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3039 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3040 args[orig_arg]);
3041 goto error;
3042 }
3043
3044 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3045 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3046
3047 cur_arg += 2;
3048 if (strcmp(args[cur_arg], "-m") == 0) {
3049 if (!*(args[cur_arg+1])) {
3050 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3051 args[orig_arg], args[cur_arg]);
3052 goto error;
3053 }
3054 if (strcmp(args[cur_arg+1], "str") == 0)
3055 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3056 else if (strcmp(args[cur_arg+1], "beg") == 0)
3057 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3058 else if (strcmp(args[cur_arg+1], "end") == 0)
3059 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3060 else if (strcmp(args[cur_arg+1], "sub") == 0)
3061 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3062 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3063 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3064 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3065 args[orig_arg]);
3066 goto error;
3067 }
3068 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3069 }
3070 else {
3071 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3072 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3073 goto error;
3074 }
3075 cur_arg += 2;
3076 }
3077 else
3078 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3079 npat = args[cur_arg];
3080
3081 if (!*(args[cur_arg+1]) ||
3082 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3083 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3084 goto next;
3085 }
3086 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3087 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3088
3089 /* Parse the value pattern, optional */
3090 if (strcmp(args[cur_arg+2], "-m") == 0) {
3091 cur_arg += 2;
3092 if (!*(args[cur_arg+1])) {
3093 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3094 args[orig_arg], args[cur_arg]);
3095 goto error;
3096 }
3097 if (strcmp(args[cur_arg+1], "str") == 0)
3098 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3099 else if (strcmp(args[cur_arg+1], "beg") == 0)
3100 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3101 else if (strcmp(args[cur_arg+1], "end") == 0)
3102 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3103 else if (strcmp(args[cur_arg+1], "sub") == 0)
3104 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3105 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3106 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3107 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3108 args[orig_arg]);
3109 goto error;
3110 }
3111 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3112 }
3113 else {
3114 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3115 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3116 goto error;
3117 }
3118 }
3119 else
3120 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3121
3122 if (!*(args[cur_arg+2])) {
3123 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3124 goto error;
3125 }
3126 vpat = args[cur_arg+2];
3127 cur_arg += 2;
3128 }
3129 else if (strcmp(args[cur_arg], "comment") == 0) {
3130 if (in_pattern) {
3131 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3132 goto error;
3133 }
3134 if (!*(args[cur_arg+1])) {
3135 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3136 goto error;
3137 }
3138 cur_arg++;
3139 free(comment);
3140 comment = strdup(args[cur_arg]);
3141 if (!comment) {
3142 memprintf(errmsg, "out of memory");
3143 goto error;
3144 }
3145 }
3146 else if (strcmp(args[cur_arg], "on-success") == 0) {
3147 if (in_pattern) {
3148 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3149 goto error;
3150 }
3151 if (!*(args[cur_arg+1])) {
3152 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3153 goto error;
3154 }
3155 cur_arg++;
3156 on_success_msg = args[cur_arg];
3157 }
3158 else if (strcmp(args[cur_arg], "on-error") == 0) {
3159 if (in_pattern) {
3160 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3161 goto error;
3162 }
3163 if (!*(args[cur_arg+1])) {
3164 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3165 goto error;
3166 }
3167 cur_arg++;
3168 on_error_msg = args[cur_arg];
3169 }
3170 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3171 if (in_pattern) {
3172 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3173 goto error;
3174 }
3175 if (!*(args[cur_arg+1])) {
3176 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3177 goto error;
3178 }
3179 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3180 ok_st = HCHK_STATUS_L7OKD;
3181 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3182 ok_st = HCHK_STATUS_L7OKCD;
3183 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3184 ok_st = HCHK_STATUS_L6OK;
3185 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3186 ok_st = HCHK_STATUS_L4OK;
3187 else {
3188 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3189 args[cur_arg], args[cur_arg+1]);
3190 goto error;
3191 }
3192 cur_arg++;
3193 }
3194 else if (strcmp(args[cur_arg], "error-status") == 0) {
3195 if (in_pattern) {
3196 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3197 goto error;
3198 }
3199 if (!*(args[cur_arg+1])) {
3200 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3201 goto error;
3202 }
3203 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3204 err_st = HCHK_STATUS_L7RSP;
3205 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3206 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003207 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3208 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003209 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3210 err_st = HCHK_STATUS_L6RSP;
3211 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3212 err_st = HCHK_STATUS_L4CON;
3213 else {
3214 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3215 args[cur_arg], args[cur_arg+1]);
3216 goto error;
3217 }
3218 cur_arg++;
3219 }
3220 else if (strcmp(args[cur_arg], "status-code") == 0) {
3221 int idx = 0;
3222
3223 if (in_pattern) {
3224 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3225 goto error;
3226 }
3227 if (!*(args[cur_arg+1])) {
3228 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3229 goto error;
3230 }
3231
3232 cur_arg++;
3233 release_sample_expr(status_expr);
3234 px->conf.args.ctx = ARGC_SRV;
3235 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003236 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003237 if (!status_expr) {
3238 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3239 goto error;
3240 }
3241 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3242 memprintf(errmsg, "error detected while parsing status-code expression : "
3243 " fetch method '%s' extracts information from '%s', "
3244 "none of which is available here.\n",
3245 args[cur_arg], sample_src_names(status_expr->fetch->use));
3246 goto error;
3247 }
3248 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3249 }
3250 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3251 if (in_pattern) {
3252 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3253 goto error;
3254 }
3255 if (!*(args[cur_arg+1])) {
3256 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3257 goto error;
3258 }
3259 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3260 tout_st = HCHK_STATUS_L7TOUT;
3261 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3262 tout_st = HCHK_STATUS_L6TOUT;
3263 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3264 tout_st = HCHK_STATUS_L4TOUT;
3265 else {
3266 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3267 args[cur_arg], args[cur_arg+1]);
3268 goto error;
3269 }
3270 cur_arg++;
3271 }
3272 else {
3273 if (proto == TCPCHK_RULES_HTTP_CHK) {
3274 bad_http_kw:
3275 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3276 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3277 }
3278 else {
3279 bad_tcp_kw:
3280 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3281 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3282 }
3283 goto error;
3284 }
3285 next:
3286 cur_arg++;
3287 }
3288
3289 chk = calloc(1, sizeof(*chk));
3290 if (!chk) {
3291 memprintf(errmsg, "out of memory");
3292 goto error;
3293 }
3294 chk->action = TCPCHK_ACT_EXPECT;
3295 LIST_INIT(&chk->expect.onerror_fmt);
3296 LIST_INIT(&chk->expect.onsuccess_fmt);
3297 chk->comment = comment; comment = NULL;
3298 chk->expect.type = type;
3299 chk->expect.min_recv = min_recv;
3300 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3301 chk->expect.ok_status = ok_st;
3302 chk->expect.err_status = err_st;
3303 chk->expect.tout_status = tout_st;
3304 chk->expect.status_expr = status_expr; status_expr = NULL;
3305
3306 if (on_success_msg) {
3307 px->conf.args.ctx = ARGC_SRV;
3308 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3309 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3310 goto error;
3311 }
3312 }
3313 if (on_error_msg) {
3314 px->conf.args.ctx = ARGC_SRV;
3315 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3316 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3317 goto error;
3318 }
3319 }
3320
3321 switch (chk->expect.type) {
3322 case TCPCHK_EXPECT_HTTP_STATUS: {
3323 const char *p = pattern;
3324 unsigned int c1,c2;
3325
3326 chk->expect.codes.codes = NULL;
3327 chk->expect.codes.num = 0;
3328 while (1) {
3329 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3330 if (*p == '-') {
3331 p++;
3332 c2 = read_uint(&p, pattern + strlen(pattern));
3333 }
3334 if (c1 > c2) {
3335 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3336 goto error;
3337 }
3338
3339 chk->expect.codes.num++;
3340 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3341 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3342 if (!chk->expect.codes.codes) {
3343 memprintf(errmsg, "out of memory");
3344 goto error;
3345 }
3346 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3347 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3348
3349 if (*p == '\0')
3350 break;
3351 if (*p != ',') {
3352 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3353 goto error;
3354 }
3355 p++;
3356 }
3357 break;
3358 }
3359 case TCPCHK_EXPECT_STRING:
3360 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003361 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003362 if (!isttest(chk->expect.data)) {
3363 memprintf(errmsg, "out of memory");
3364 goto error;
3365 }
3366 break;
3367 case TCPCHK_EXPECT_BINARY: {
3368 int len = chk->expect.data.len;
3369
3370 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3371 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3372 goto error;
3373 }
3374 chk->expect.data.len = len;
3375 break;
3376 }
3377 case TCPCHK_EXPECT_STRING_REGEX:
3378 case TCPCHK_EXPECT_BINARY_REGEX:
3379 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3380 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3381 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3382 if (!chk->expect.regex)
3383 goto error;
3384 break;
3385
3386 case TCPCHK_EXPECT_STRING_LF:
3387 case TCPCHK_EXPECT_BINARY_LF:
3388 case TCPCHK_EXPECT_HTTP_BODY_LF:
3389 LIST_INIT(&chk->expect.fmt);
3390 px->conf.args.ctx = ARGC_SRV;
3391 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3392 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3393 goto error;
3394 }
3395 break;
3396
3397 case TCPCHK_EXPECT_HTTP_HEADER:
3398 if (!npat) {
3399 memprintf(errmsg, "unexpected error, undefined header name pattern");
3400 goto error;
3401 }
3402 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3403 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3404 if (!chk->expect.hdr.name_re)
3405 goto error;
3406 }
3407 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3408 px->conf.args.ctx = ARGC_SRV;
3409 LIST_INIT(&chk->expect.hdr.name_fmt);
3410 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3411 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3412 goto error;
3413 }
3414 }
3415 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003416 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003417 if (!isttest(chk->expect.hdr.name)) {
3418 memprintf(errmsg, "out of memory");
3419 goto error;
3420 }
3421 }
3422
3423 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3424 chk->expect.hdr.value = IST_NULL;
3425 break;
3426 }
3427
3428 if (!vpat) {
3429 memprintf(errmsg, "unexpected error, undefined header value pattern");
3430 goto error;
3431 }
3432 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3433 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3434 if (!chk->expect.hdr.value_re)
3435 goto error;
3436 }
3437 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3438 px->conf.args.ctx = ARGC_SRV;
3439 LIST_INIT(&chk->expect.hdr.value_fmt);
3440 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3441 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3442 goto error;
3443 }
3444 }
3445 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003446 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003447 if (!isttest(chk->expect.hdr.value)) {
3448 memprintf(errmsg, "out of memory");
3449 goto error;
3450 }
3451 }
3452
3453 break;
3454 case TCPCHK_EXPECT_CUSTOM:
3455 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3456 break;
3457 case TCPCHK_EXPECT_UNDEF:
3458 memprintf(errmsg, "pattern not found");
3459 goto error;
3460 }
3461
3462 /* All tcp-check expect points back to the first inverse expect rule in
3463 * a chain of one or more expect rule, potentially itself.
3464 */
3465 chk->expect.head = chk;
3466 list_for_each_entry_rev(prev_check, rules, list) {
3467 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3468 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3469 chk->expect.head = prev_check;
3470 continue;
3471 }
3472 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3473 break;
3474 }
3475 return chk;
3476
3477 error:
3478 free_tcpcheck(chk, 0);
3479 free(comment);
3480 release_sample_expr(status_expr);
3481 return NULL;
3482}
3483
3484/* Overwrites fields of the old http send rule with those of the new one. When
3485 * replaced, old values are freed and replaced by the new ones. New values are
3486 * not copied but transferred. At the end <new> should be empty and can be
3487 * safely released. This function never fails.
3488 */
3489void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3490{
3491 struct logformat_node *lf, *lfb;
3492 struct tcpcheck_http_hdr *hdr, *bhdr;
3493
3494
3495 if (new->send.http.meth.str.area) {
3496 free(old->send.http.meth.str.area);
3497 old->send.http.meth.meth = new->send.http.meth.meth;
3498 old->send.http.meth.str.area = new->send.http.meth.str.area;
3499 old->send.http.meth.str.data = new->send.http.meth.str.data;
3500 new->send.http.meth.str = BUF_NULL;
3501 }
3502
3503 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3504 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3505 istfree(&old->send.http.uri);
3506 else
3507 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3508 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3509 old->send.http.uri = new->send.http.uri;
3510 new->send.http.uri = IST_NULL;
3511 }
3512 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3513 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3514 istfree(&old->send.http.uri);
3515 else
3516 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3517 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3518 LIST_INIT(&old->send.http.uri_fmt);
3519 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003520 LIST_DELETE(&lf->list);
3521 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003522 }
3523 }
3524
3525 if (isttest(new->send.http.vsn)) {
3526 istfree(&old->send.http.vsn);
3527 old->send.http.vsn = new->send.http.vsn;
3528 new->send.http.vsn = IST_NULL;
3529 }
3530
3531 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3532 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003533 LIST_DELETE(&hdr->list);
3534 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003535 }
3536
3537 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3538 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3539 istfree(&old->send.http.body);
3540 else
3541 free_tcpcheck_fmt(&old->send.http.body_fmt);
3542 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3543 old->send.http.body = new->send.http.body;
3544 new->send.http.body = IST_NULL;
3545 }
3546 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3547 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3548 istfree(&old->send.http.body);
3549 else
3550 free_tcpcheck_fmt(&old->send.http.body_fmt);
3551 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3552 LIST_INIT(&old->send.http.body_fmt);
3553 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003554 LIST_DELETE(&lf->list);
3555 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003556 }
3557 }
3558}
3559
3560/* Internal function used to add an http-check rule in a list during the config
3561 * parsing step. Depending on its type, and the previously inserted rules, a
3562 * specific action may be performed or an error may be reported. This functions
3563 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3564 * message.
3565 */
3566int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3567{
3568 struct tcpcheck_rule *r;
3569
3570 /* the implicit send rule coming from an "option httpchk" line must be
3571 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003572 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003573 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003574 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003575 * sure the ruleset remains valid.
3576 */
3577
3578 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3579 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3580 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3581 * following tests are performed :
3582 *
3583 * 1- If there is no such rule or if it is not a send rule, the implicit send
3584 * rule is pushed in front of the ruleset
3585 *
3586 * 2- If it is another implicit send rule, it is replaced with the new one.
3587 *
3588 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3589 * both, overwriting the old send rule (the explicit one) with info of the
3590 * new send rule (the implicit one).
3591 */
3592 r = get_first_tcpcheck_rule(rules);
3593 if (r && r->action == TCPCHK_ACT_CONNECT)
3594 r = get_next_tcpcheck_rule(rules, r);
3595 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003596 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003597 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003598 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003599 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003600 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003601 }
3602 else {
3603 tcpcheck_overwrite_send_http_rule(r, chk);
3604 free_tcpcheck(chk, 0);
3605 }
3606 }
3607 else {
3608 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3609 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3610 * with an existing implicit send rule, if any. At the end, if there is no error,
3611 * the rule is appended to the list.
3612 */
3613
3614 r = get_last_tcpcheck_rule(rules);
3615 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3616 /* no error */;
3617 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3618 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3619 chk->index+1);
3620 return 0;
3621 }
3622 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3623 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3624 chk->index+1);
3625 return 0;
3626 }
3627 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3628 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3629 chk->index+1);
3630 return 0;
3631 }
3632
3633 if (chk->action == TCPCHK_ACT_SEND) {
3634 r = get_first_tcpcheck_rule(rules);
3635 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3636 tcpcheck_overwrite_send_http_rule(r, chk);
3637 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003638 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003639 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3640 chk = r;
3641 }
3642 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003643 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003644 }
3645 return 1;
3646}
3647
3648/* Check tcp-check health-check configuration for the proxy <px>. */
3649static int check_proxy_tcpcheck(struct proxy *px)
3650{
3651 struct tcpcheck_rule *chk, *back;
3652 char *comment = NULL, *errmsg = NULL;
3653 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003654 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003655
3656 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3657 deinit_proxy_tcpcheck(px);
3658 goto out;
3659 }
3660
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003661 ha_free(&px->check_command);
3662 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003663
3664 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003665 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003666 ret |= ERR_ALERT | ERR_FATAL;
3667 goto out;
3668 }
3669
3670 /* HTTP ruleset only : */
3671 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3672 struct tcpcheck_rule *next;
3673
3674 /* move remaining implicit send rule from "option httpchk" line to the right place.
3675 * If such rule exists, it must be the first one. In this case, the rule is moved
3676 * after the first connect rule, if any. Otherwise, nothing is done.
3677 */
3678 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3679 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3680 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3681 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003682 LIST_DELETE(&chk->list);
3683 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003684 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003685 }
3686 }
3687
3688 /* add implicit expect rule if the last one is a send. It is inherited from previous
3689 * versions where the http expect rule was optional. Now it is possible to chained
3690 * send/expect rules but the last expect may still be implicit.
3691 */
3692 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3693 if (chk && chk->action == TCPCHK_ACT_SEND) {
3694 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3695 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3696 px->conf.file, px->conf.line, &errmsg);
3697 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003698 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003699 "(%s).\n", px->id, errmsg);
3700 free(errmsg);
3701 ret |= ERR_ALERT | ERR_FATAL;
3702 goto out;
3703 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003704 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003705 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003706 }
3707 }
3708
3709 /* For all ruleset: */
3710
3711 /* If there is no connect rule preceding all send / expect rules, an
3712 * implicit one is inserted before all others.
3713 */
3714 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3715 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3716 chk = calloc(1, sizeof(*chk));
3717 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003718 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003719 "(out of memory).\n", px->id);
3720 ret |= ERR_ALERT | ERR_FATAL;
3721 goto out;
3722 }
3723 chk->action = TCPCHK_ACT_CONNECT;
3724 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003725 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003726 }
3727
3728 /* Remove all comment rules. To do so, when a such rule is found, the
3729 * comment is assigned to the following rule(s).
3730 */
3731 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003732 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3733 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003734
3735 prev_action = chk->action;
3736 switch (chk->action) {
3737 case TCPCHK_ACT_COMMENT:
3738 free(comment);
3739 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003740 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003741 free(chk);
3742 break;
3743 case TCPCHK_ACT_CONNECT:
3744 if (!chk->comment && comment)
3745 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003746 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003747 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003748 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003749 break;
3750 case TCPCHK_ACT_SEND:
3751 case TCPCHK_ACT_EXPECT:
3752 if (!chk->comment && comment)
3753 chk->comment = strdup(comment);
3754 break;
3755 }
3756 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003757 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003758
3759 out:
3760 return ret;
3761}
3762
3763void deinit_proxy_tcpcheck(struct proxy *px)
3764{
3765 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3766 px->tcpcheck_rules.flags = 0;
3767 px->tcpcheck_rules.list = NULL;
3768}
3769
3770static void deinit_tcpchecks()
3771{
3772 struct tcpcheck_ruleset *rs;
3773 struct tcpcheck_rule *r, *rb;
3774 struct ebpt_node *node, *next;
3775
3776 node = ebpt_first(&shared_tcpchecks);
3777 while (node) {
3778 next = ebpt_next(node);
3779 ebpt_delete(node);
3780 free(node->key);
3781 rs = container_of(node, typeof(*rs), node);
3782 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003783 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003784 free_tcpcheck(r, 0);
3785 }
3786 free(rs);
3787 node = next;
3788 }
3789}
3790
3791int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3792{
3793 struct tcpcheck_rule *tcpcheck, *prev_check;
3794 struct tcpcheck_expect *expect;
3795
Willy Tarreau6922e552021-03-22 21:11:45 +01003796 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003797 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003798 tcpcheck->action = TCPCHK_ACT_EXPECT;
3799
3800 expect = &tcpcheck->expect;
3801 expect->type = TCPCHK_EXPECT_STRING;
3802 LIST_INIT(&expect->onerror_fmt);
3803 LIST_INIT(&expect->onsuccess_fmt);
3804 expect->ok_status = HCHK_STATUS_L7OKD;
3805 expect->err_status = HCHK_STATUS_L7RSP;
3806 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003807 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003808 if (!isttest(expect->data)) {
3809 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3810 return 0;
3811 }
3812
3813 /* All tcp-check expect points back to the first inverse expect rule
3814 * in a chain of one or more expect rule, potentially itself.
3815 */
3816 tcpcheck->expect.head = tcpcheck;
3817 list_for_each_entry_rev(prev_check, rules->list, list) {
3818 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3819 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3820 tcpcheck->expect.head = prev_check;
3821 continue;
3822 }
3823 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3824 break;
3825 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003826 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003827 return 1;
3828}
3829
3830int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3831{
3832 struct tcpcheck_rule *tcpcheck;
3833 struct tcpcheck_send *send;
3834 const char *in;
3835 char *dst;
3836 int i;
3837
Willy Tarreau6922e552021-03-22 21:11:45 +01003838 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003839 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003840 tcpcheck->action = TCPCHK_ACT_SEND;
3841
3842 send = &tcpcheck->send;
3843 send->type = TCPCHK_SEND_STRING;
3844
3845 for (i = 0; strs[i]; i++)
3846 send->data.len += strlen(strs[i]);
3847
3848 send->data.ptr = malloc(istlen(send->data) + 1);
3849 if (!isttest(send->data)) {
3850 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3851 return 0;
3852 }
3853
3854 dst = istptr(send->data);
3855 for (i = 0; strs[i]; i++)
3856 for (in = strs[i]; (*dst = *in++); dst++);
3857 *dst = 0;
3858
Willy Tarreau2b718102021-04-21 07:32:39 +02003859 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003860 return 1;
3861}
3862
3863/* Parses the "tcp-check" proxy keyword */
3864static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003865 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003866 char **errmsg)
3867{
3868 struct tcpcheck_ruleset *rs = NULL;
3869 struct tcpcheck_rule *chk = NULL;
3870 int index, cur_arg, ret = 0;
3871
3872 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3873 ret = 1;
3874
3875 /* Deduce the ruleset name from the proxy info */
3876 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3877 ((curpx == defpx) ? "defaults" : curpx->id),
3878 curpx->conf.file, curpx->conf.line);
3879
3880 rs = find_tcpcheck_ruleset(b_orig(&trash));
3881 if (rs == NULL) {
3882 rs = create_tcpcheck_ruleset(b_orig(&trash));
3883 if (rs == NULL) {
3884 memprintf(errmsg, "out of memory.\n");
3885 goto error;
3886 }
3887 }
3888
3889 index = 0;
3890 if (!LIST_ISEMPTY(&rs->rules)) {
3891 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3892 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003893 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003894 }
3895
3896 cur_arg = 1;
3897 if (strcmp(args[cur_arg], "connect") == 0)
3898 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3899 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3900 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3901 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3902 else if (strcmp(args[cur_arg], "expect") == 0)
3903 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3904 else if (strcmp(args[cur_arg], "comment") == 0)
3905 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3906 else {
3907 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3908
3909 if (!kw) {
3910 action_kw_tcp_check_build_list(&trash);
3911 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3912 "%s%s. but got '%s'",
3913 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3914 goto error;
3915 }
3916 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3917 }
3918
3919 if (!chk) {
3920 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3921 goto error;
3922 }
3923 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3924
3925 /* No error: add the tcp-check rule in the list */
3926 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003927 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003928
3929 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3930 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3931 /* Use this ruleset if the proxy already has tcp-check enabled */
3932 curpx->tcpcheck_rules.list = &rs->rules;
3933 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3934 }
3935 else {
3936 /* mark this ruleset as unused for now */
3937 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3938 }
3939
3940 return ret;
3941
3942 error:
3943 free_tcpcheck(chk, 0);
3944 free_tcpcheck_ruleset(rs);
3945 return -1;
3946}
3947
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003948/* Parses the "http-check" proxy keyword */
3949static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003950 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003951 char **errmsg)
3952{
3953 struct tcpcheck_ruleset *rs = NULL;
3954 struct tcpcheck_rule *chk = NULL;
3955 int index, cur_arg, ret = 0;
3956
3957 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3958 ret = 1;
3959
3960 cur_arg = 1;
3961 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3962 /* enable a graceful server shutdown on an HTTP 404 response */
3963 curpx->options |= PR_O_DISABLE404;
3964 if (too_many_args(1, args, errmsg, NULL))
3965 goto error;
3966 goto out;
3967 }
3968 else if (strcmp(args[cur_arg], "send-state") == 0) {
3969 /* enable emission of the apparent state of a server in HTTP checks */
3970 curpx->options2 |= PR_O2_CHK_SNDST;
3971 if (too_many_args(1, args, errmsg, NULL))
3972 goto error;
3973 goto out;
3974 }
3975
3976 /* Deduce the ruleset name from the proxy info */
3977 chunk_printf(&trash, "*http-check-%s_%s-%d",
3978 ((curpx == defpx) ? "defaults" : curpx->id),
3979 curpx->conf.file, curpx->conf.line);
3980
3981 rs = find_tcpcheck_ruleset(b_orig(&trash));
3982 if (rs == NULL) {
3983 rs = create_tcpcheck_ruleset(b_orig(&trash));
3984 if (rs == NULL) {
3985 memprintf(errmsg, "out of memory.\n");
3986 goto error;
3987 }
3988 }
3989
3990 index = 0;
3991 if (!LIST_ISEMPTY(&rs->rules)) {
3992 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3993 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
3994 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003995 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003996 }
3997
3998 if (strcmp(args[cur_arg], "connect") == 0)
3999 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4000 else if (strcmp(args[cur_arg], "send") == 0)
4001 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4002 else if (strcmp(args[cur_arg], "expect") == 0)
4003 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4004 file, line, errmsg);
4005 else if (strcmp(args[cur_arg], "comment") == 0)
4006 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4007 else {
4008 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4009
4010 if (!kw) {
4011 action_kw_tcp_check_build_list(&trash);
4012 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4013 " 'send', 'expect'%s%s. but got '%s'",
4014 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4015 goto error;
4016 }
4017 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4018 }
4019
4020 if (!chk) {
4021 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4022 goto error;
4023 }
4024 ret = (*errmsg != NULL); /* Handle warning */
4025
4026 chk->index = index;
4027 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4028 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4029 /* Use this ruleset if the proxy already has http-check enabled */
4030 curpx->tcpcheck_rules.list = &rs->rules;
4031 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4032 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4033 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4034 curpx->tcpcheck_rules.list = NULL;
4035 goto error;
4036 }
4037 }
4038 else {
4039 /* mark this ruleset as unused for now */
4040 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004041 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004042 }
4043
4044 out:
4045 return ret;
4046
4047 error:
4048 free_tcpcheck(chk, 0);
4049 free_tcpcheck_ruleset(rs);
4050 return -1;
4051}
4052
4053/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004054int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004055 const char *file, int line)
4056{
4057 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4058 static char *redis_res = "+PONG\r\n";
4059
4060 struct tcpcheck_ruleset *rs = NULL;
4061 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4062 struct tcpcheck_rule *chk;
4063 char *errmsg = NULL;
4064 int err_code = 0;
4065
4066 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4067 err_code |= ERR_WARN;
4068
4069 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4070 goto out;
4071
4072 curpx->options2 &= ~PR_O2_CHK_ANY;
4073 curpx->options2 |= PR_O2_TCPCHK_CHK;
4074
4075 free_tcpcheck_vars(&rules->preset_vars);
4076 rules->list = NULL;
4077 rules->flags = 0;
4078
4079 rs = find_tcpcheck_ruleset("*redis-check");
4080 if (rs)
4081 goto ruleset_found;
4082
4083 rs = create_tcpcheck_ruleset("*redis-check");
4084 if (rs == NULL) {
4085 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4086 goto error;
4087 }
4088
4089 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4090 1, curpx, &rs->rules, file, line, &errmsg);
4091 if (!chk) {
4092 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4093 goto error;
4094 }
4095 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004096 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004097
4098 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4099 "error-status", "L7STS",
4100 "on-error", "%[res.payload(0,0),cut_crlf]",
4101 "on-success", "Redis server is ok",
4102 ""},
4103 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4104 if (!chk) {
4105 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4106 goto error;
4107 }
4108 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004109 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004110
4111 ruleset_found:
4112 rules->list = &rs->rules;
4113 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4114 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4115
4116 out:
4117 free(errmsg);
4118 return err_code;
4119
4120 error:
4121 free_tcpcheck_ruleset(rs);
4122 err_code |= ERR_ALERT | ERR_FATAL;
4123 goto out;
4124}
4125
4126
4127/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004128int 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 +01004129 const char *file, int line)
4130{
4131 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4132 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4133 *
4134 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4135 */
4136 static char sslv3_client_hello[] = {
4137 "16" /* ContentType : 0x16 = Handshake */
4138 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4139 "0079" /* ContentLength : 0x79 bytes after this one */
4140 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4141 "000075" /* HandshakeLength : 0x75 bytes after this one */
4142 "0300" /* Hello Version : 0x0300 = v3 */
4143 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4144 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4145 "00" /* Session ID length : empty (no session ID) */
4146 "004E" /* Cipher Suite Length : 78 bytes after this one */
4147 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4148 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4149 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4150 "000D" "000E" "000F" "0010" /* various bit lengths, */
4151 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4152 "0015" "0016" "0017" "0018"
4153 "0019" "001A" "001B" "002F"
4154 "0030" "0031" "0032" "0033"
4155 "0034" "0035" "0036" "0037"
4156 "0038" "0039" "003A"
4157 "01" /* Compression Length : 0x01 = 1 byte for types */
4158 "00" /* Compression Type : 0x00 = NULL compression */
4159 };
4160
4161 struct tcpcheck_ruleset *rs = NULL;
4162 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4163 struct tcpcheck_rule *chk;
4164 char *errmsg = NULL;
4165 int err_code = 0;
4166
4167 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4168 err_code |= ERR_WARN;
4169
4170 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4171 goto out;
4172
4173 curpx->options2 &= ~PR_O2_CHK_ANY;
4174 curpx->options2 |= PR_O2_TCPCHK_CHK;
4175
4176 free_tcpcheck_vars(&rules->preset_vars);
4177 rules->list = NULL;
4178 rules->flags = 0;
4179
4180 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4181 if (rs)
4182 goto ruleset_found;
4183
4184 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4185 if (rs == NULL) {
4186 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4187 goto error;
4188 }
4189
4190 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4191 1, curpx, &rs->rules, file, line, &errmsg);
4192 if (!chk) {
4193 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4194 goto error;
4195 }
4196 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004197 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004198
4199 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4200 "min-recv", "5", "ok-status", "L6OK",
4201 "error-status", "L6RSP", "tout-status", "L6TOUT",
4202 ""},
4203 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4204 if (!chk) {
4205 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4206 goto error;
4207 }
4208 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004209 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004210
4211 ruleset_found:
4212 rules->list = &rs->rules;
4213 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4214 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4215
4216 out:
4217 free(errmsg);
4218 return err_code;
4219
4220 error:
4221 free_tcpcheck_ruleset(rs);
4222 err_code |= ERR_ALERT | ERR_FATAL;
4223 goto out;
4224}
4225
4226/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004227int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004228 const char *file, int line)
4229{
4230 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4231
4232 struct tcpcheck_ruleset *rs = NULL;
4233 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4234 struct tcpcheck_rule *chk;
4235 struct tcpcheck_var *var = NULL;
4236 char *cmd = NULL, *errmsg = NULL;
4237 int err_code = 0;
4238
4239 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4240 err_code |= ERR_WARN;
4241
4242 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4243 goto out;
4244
4245 curpx->options2 &= ~PR_O2_CHK_ANY;
4246 curpx->options2 |= PR_O2_TCPCHK_CHK;
4247
4248 free_tcpcheck_vars(&rules->preset_vars);
4249 rules->list = NULL;
4250 rules->flags = 0;
4251
4252 cur_arg += 2;
4253 if (*args[cur_arg] && *args[cur_arg+1] &&
4254 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4255 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4256 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4257 if (cmd)
4258 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4259 }
4260 else {
4261 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4262 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4263 cmd = strdup("HELO localhost");
4264 }
4265
4266 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4267 if (cmd == NULL || var == NULL) {
4268 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4269 goto error;
4270 }
4271 var->data.type = SMP_T_STR;
4272 var->data.u.str.area = cmd;
4273 var->data.u.str.data = strlen(cmd);
4274 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004275 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004276 cmd = NULL;
4277 var = NULL;
4278
4279 rs = find_tcpcheck_ruleset("*smtp-check");
4280 if (rs)
4281 goto ruleset_found;
4282
4283 rs = create_tcpcheck_ruleset("*smtp-check");
4284 if (rs == NULL) {
4285 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4286 goto error;
4287 }
4288
4289 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4290 1, curpx, &rs->rules, file, line, &errmsg);
4291 if (!chk) {
4292 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4293 goto error;
4294 }
4295 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004296 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004297
4298 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4299 "min-recv", "4",
4300 "error-status", "L7RSP",
4301 "on-error", "%[res.payload(0,0),cut_crlf]",
4302 ""},
4303 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4304 if (!chk) {
4305 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4306 goto error;
4307 }
4308 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004309 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004310
4311 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4312 "min-recv", "4",
4313 "error-status", "L7STS",
4314 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4315 "status-code", "res.payload(0,3)",
4316 ""},
4317 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4318 if (!chk) {
4319 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4320 goto error;
4321 }
4322 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004323 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004324
4325 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4326 1, curpx, &rs->rules, file, line, &errmsg);
4327 if (!chk) {
4328 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4329 goto error;
4330 }
4331 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004332 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004333
4334 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4335 "min-recv", "4",
4336 "error-status", "L7STS",
4337 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4338 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4339 "status-code", "res.payload(0,3)",
4340 ""},
4341 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4342 if (!chk) {
4343 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4344 goto error;
4345 }
4346 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004347 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004348
4349 ruleset_found:
4350 rules->list = &rs->rules;
4351 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4352 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4353
4354 out:
4355 free(errmsg);
4356 return err_code;
4357
4358 error:
4359 free(cmd);
4360 free(var);
4361 free_tcpcheck_vars(&rules->preset_vars);
4362 free_tcpcheck_ruleset(rs);
4363 err_code |= ERR_ALERT | ERR_FATAL;
4364 goto out;
4365}
4366
4367/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004368int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004369 const char *file, int line)
4370{
4371 static char pgsql_req[] = {
4372 "%[var(check.plen),htonl,hex]" /* The packet length*/
4373 "00030000" /* the version 3.0 */
4374 "7573657200" /* "user" key */
4375 "%[var(check.username),hex]00" /* the username */
4376 "00"
4377 };
4378
4379 struct tcpcheck_ruleset *rs = NULL;
4380 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4381 struct tcpcheck_rule *chk;
4382 struct tcpcheck_var *var = NULL;
4383 char *user = NULL, *errmsg = NULL;
4384 size_t packetlen = 0;
4385 int err_code = 0;
4386
4387 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4388 err_code |= ERR_WARN;
4389
4390 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4391 goto out;
4392
4393 curpx->options2 &= ~PR_O2_CHK_ANY;
4394 curpx->options2 |= PR_O2_TCPCHK_CHK;
4395
4396 free_tcpcheck_vars(&rules->preset_vars);
4397 rules->list = NULL;
4398 rules->flags = 0;
4399
4400 cur_arg += 2;
4401 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4402 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4403 file, line, args[0], args[1]);
4404 goto error;
4405 }
4406 if (strcmp(args[cur_arg], "user") == 0) {
4407 packetlen = 15 + strlen(args[cur_arg+1]);
4408 user = strdup(args[cur_arg+1]);
4409
4410 var = create_tcpcheck_var(ist("check.username"));
4411 if (user == NULL || var == NULL) {
4412 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4413 goto error;
4414 }
4415 var->data.type = SMP_T_STR;
4416 var->data.u.str.area = user;
4417 var->data.u.str.data = strlen(user);
4418 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004419 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004420 user = NULL;
4421 var = NULL;
4422
4423 var = create_tcpcheck_var(ist("check.plen"));
4424 if (var == NULL) {
4425 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4426 goto error;
4427 }
4428 var->data.type = SMP_T_SINT;
4429 var->data.u.sint = packetlen;
4430 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004431 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004432 var = NULL;
4433 }
4434 else {
4435 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4436 file, line, args[0], args[1]);
4437 goto error;
4438 }
4439
4440 rs = find_tcpcheck_ruleset("*pgsql-check");
4441 if (rs)
4442 goto ruleset_found;
4443
4444 rs = create_tcpcheck_ruleset("*pgsql-check");
4445 if (rs == NULL) {
4446 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4447 goto error;
4448 }
4449
4450 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4451 1, curpx, &rs->rules, file, line, &errmsg);
4452 if (!chk) {
4453 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4454 goto error;
4455 }
4456 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004457 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004458
4459 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4460 1, curpx, &rs->rules, file, line, &errmsg);
4461 if (!chk) {
4462 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4463 goto error;
4464 }
4465 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004466 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004467
4468 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4469 "min-recv", "5",
4470 "error-status", "L7RSP",
4471 "on-error", "%[res.payload(6,0)]",
4472 ""},
4473 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4474 if (!chk) {
4475 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4476 goto error;
4477 }
4478 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004479 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004480
4481 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4482 "min-recv", "9",
4483 "error-status", "L7STS",
4484 "on-success", "PostgreSQL server is ok",
4485 "on-error", "PostgreSQL unknown error",
4486 ""},
4487 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4488 if (!chk) {
4489 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4490 goto error;
4491 }
4492 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004493 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004494
4495 ruleset_found:
4496 rules->list = &rs->rules;
4497 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4498 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4499
4500 out:
4501 free(errmsg);
4502 return err_code;
4503
4504 error:
4505 free(user);
4506 free(var);
4507 free_tcpcheck_vars(&rules->preset_vars);
4508 free_tcpcheck_ruleset(rs);
4509 err_code |= ERR_ALERT | ERR_FATAL;
4510 goto out;
4511}
4512
4513
4514/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004515int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004516 const char *file, int line)
4517{
4518 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4519 * const char mysql40_client_auth_pkt[] = {
4520 * "\x0e\x00\x00" // packet length
4521 * "\x01" // packet number
4522 * "\x00\x00" // client capabilities
4523 * "\x00\x00\x01" // max packet
4524 * "haproxy\x00" // username (null terminated string)
4525 * "\x00" // filler (always 0x00)
4526 * "\x01\x00\x00" // packet length
4527 * "\x00" // packet number
4528 * "\x01" // COM_QUIT command
4529 * };
4530 */
4531 static char mysql40_rsname[] = "*mysql40-check";
4532 static char mysql40_req[] = {
4533 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4534 "0080" /* client capabilities */
4535 "000001" /* max packet */
4536 "%[var(check.username),hex]00" /* the username */
4537 "00" /* filler (always 0x00) */
4538 "010000" /* packet length*/
4539 "00" /* sequence ID */
4540 "01" /* COM_QUIT command */
4541 };
4542
4543 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4544 * const char mysql41_client_auth_pkt[] = {
4545 * "\x0e\x00\x00\" // packet length
4546 * "\x01" // packet number
4547 * "\x00\x00\x00\x00" // client capabilities
4548 * "\x00\x00\x00\x01" // max packet
4549 * "\x21" // character set (UTF-8)
4550 * char[23] // All zeroes
4551 * "haproxy\x00" // username (null terminated string)
4552 * "\x00" // filler (always 0x00)
4553 * "\x01\x00\x00" // packet length
4554 * "\x00" // packet number
4555 * "\x01" // COM_QUIT command
4556 * };
4557 */
4558 static char mysql41_rsname[] = "*mysql41-check";
4559 static char mysql41_req[] = {
4560 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4561 "00820000" /* client capabilities */
4562 "00800001" /* max packet */
4563 "21" /* character set (UTF-8) */
4564 "000000000000000000000000" /* 23 bytes, al zeroes */
4565 "0000000000000000000000"
4566 "%[var(check.username),hex]00" /* the username */
4567 "00" /* filler (always 0x00) */
4568 "010000" /* packet length*/
4569 "00" /* sequence ID */
4570 "01" /* COM_QUIT command */
4571 };
4572
4573 struct tcpcheck_ruleset *rs = NULL;
4574 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4575 struct tcpcheck_rule *chk;
4576 struct tcpcheck_var *var = NULL;
4577 char *mysql_rsname = "*mysql-check";
4578 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4579 int index = 0, err_code = 0;
4580
4581 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4582 err_code |= ERR_WARN;
4583
4584 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4585 goto out;
4586
4587 curpx->options2 &= ~PR_O2_CHK_ANY;
4588 curpx->options2 |= PR_O2_TCPCHK_CHK;
4589
4590 free_tcpcheck_vars(&rules->preset_vars);
4591 rules->list = NULL;
4592 rules->flags = 0;
4593
4594 cur_arg += 2;
4595 if (*args[cur_arg]) {
4596 int packetlen, userlen;
4597
4598 if (strcmp(args[cur_arg], "user") != 0) {
4599 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4600 file, line, args[0], args[1], args[cur_arg]);
4601 goto error;
4602 }
4603
4604 if (*(args[cur_arg+1]) == 0) {
4605 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4606 file, line, args[0], args[1], args[cur_arg]);
4607 goto error;
4608 }
4609
4610 hdr = calloc(4, sizeof(*hdr));
4611 user = strdup(args[cur_arg+1]);
4612 userlen = strlen(args[cur_arg+1]);
4613
4614 if (hdr == NULL || user == NULL) {
4615 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4616 goto error;
4617 }
4618
4619 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4620 packetlen = userlen + 7 + 27;
4621 mysql_req = mysql41_req;
4622 mysql_rsname = mysql41_rsname;
4623 }
4624 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4625 packetlen = userlen + 7;
4626 mysql_req = mysql40_req;
4627 mysql_rsname = mysql40_rsname;
4628 }
4629 else {
4630 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4631 file, line, args[cur_arg], args[cur_arg+2]);
4632 goto error;
4633 }
4634
4635 hdr[0] = (unsigned char)(packetlen & 0xff);
4636 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4637 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4638 hdr[3] = 1;
4639
4640 var = create_tcpcheck_var(ist("check.header"));
4641 if (var == NULL) {
4642 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4643 goto error;
4644 }
4645 var->data.type = SMP_T_STR;
4646 var->data.u.str.area = hdr;
4647 var->data.u.str.data = 4;
4648 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004649 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004650 hdr = NULL;
4651 var = NULL;
4652
4653 var = create_tcpcheck_var(ist("check.username"));
4654 if (var == NULL) {
4655 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4656 goto error;
4657 }
4658 var->data.type = SMP_T_STR;
4659 var->data.u.str.area = user;
4660 var->data.u.str.data = strlen(user);
4661 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004662 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004663 user = NULL;
4664 var = NULL;
4665 }
4666
4667 rs = find_tcpcheck_ruleset(mysql_rsname);
4668 if (rs)
4669 goto ruleset_found;
4670
4671 rs = create_tcpcheck_ruleset(mysql_rsname);
4672 if (rs == NULL) {
4673 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4674 goto error;
4675 }
4676
4677 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4678 1, curpx, &rs->rules, file, line, &errmsg);
4679 if (!chk) {
4680 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4681 goto error;
4682 }
4683 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004684 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004685
4686 if (mysql_req) {
4687 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
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
4697 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4698 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4699 if (!chk) {
4700 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4701 goto error;
4702 }
4703 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4704 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004705 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004706
4707 if (mysql_req) {
4708 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4709 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4710 if (!chk) {
4711 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4712 goto error;
4713 }
4714 chk->expect.custom = tcpcheck_mysql_expect_ok;
4715 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004716 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004717 }
4718
4719 ruleset_found:
4720 rules->list = &rs->rules;
4721 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4722 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4723
4724 out:
4725 free(errmsg);
4726 return err_code;
4727
4728 error:
4729 free(hdr);
4730 free(user);
4731 free(var);
4732 free_tcpcheck_vars(&rules->preset_vars);
4733 free_tcpcheck_ruleset(rs);
4734 err_code |= ERR_ALERT | ERR_FATAL;
4735 goto out;
4736}
4737
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004738int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004739 const char *file, int line)
4740{
4741 static char *ldap_req = "300C020101600702010304008000";
4742
4743 struct tcpcheck_ruleset *rs = NULL;
4744 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4745 struct tcpcheck_rule *chk;
4746 char *errmsg = NULL;
4747 int err_code = 0;
4748
4749 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4750 err_code |= ERR_WARN;
4751
4752 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4753 goto out;
4754
4755 curpx->options2 &= ~PR_O2_CHK_ANY;
4756 curpx->options2 |= PR_O2_TCPCHK_CHK;
4757
4758 free_tcpcheck_vars(&rules->preset_vars);
4759 rules->list = NULL;
4760 rules->flags = 0;
4761
4762 rs = find_tcpcheck_ruleset("*ldap-check");
4763 if (rs)
4764 goto ruleset_found;
4765
4766 rs = create_tcpcheck_ruleset("*ldap-check");
4767 if (rs == NULL) {
4768 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4769 goto error;
4770 }
4771
4772 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4773 1, curpx, &rs->rules, file, line, &errmsg);
4774 if (!chk) {
4775 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4776 goto error;
4777 }
4778 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004779 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004780
4781 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4782 "min-recv", "14",
4783 "on-error", "Not LDAPv3 protocol",
4784 ""},
4785 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4786 if (!chk) {
4787 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4788 goto error;
4789 }
4790 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004791 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004792
4793 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4794 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4795 if (!chk) {
4796 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4797 goto error;
4798 }
4799 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4800 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004801 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004802
4803 ruleset_found:
4804 rules->list = &rs->rules;
4805 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4806 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4807
4808 out:
4809 free(errmsg);
4810 return err_code;
4811
4812 error:
4813 free_tcpcheck_ruleset(rs);
4814 err_code |= ERR_ALERT | ERR_FATAL;
4815 goto out;
4816}
4817
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004818int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004819 const char *file, int line)
4820{
4821 struct tcpcheck_ruleset *rs = NULL;
4822 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4823 struct tcpcheck_rule *chk;
4824 char *spop_req = NULL;
4825 char *errmsg = NULL;
4826 int spop_len = 0, err_code = 0;
4827
4828 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4829 err_code |= ERR_WARN;
4830
4831 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4832 goto out;
4833
4834 curpx->options2 &= ~PR_O2_CHK_ANY;
4835 curpx->options2 |= PR_O2_TCPCHK_CHK;
4836
4837 free_tcpcheck_vars(&rules->preset_vars);
4838 rules->list = NULL;
4839 rules->flags = 0;
4840
4841
4842 rs = find_tcpcheck_ruleset("*spop-check");
4843 if (rs)
4844 goto ruleset_found;
4845
4846 rs = create_tcpcheck_ruleset("*spop-check");
4847 if (rs == NULL) {
4848 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4849 goto error;
4850 }
4851
4852 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4853 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4854 goto error;
4855 }
4856 chunk_reset(&trash);
4857 dump_binary(&trash, spop_req, spop_len);
4858 trash.area[trash.data] = '\0';
4859
4860 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4861 1, curpx, &rs->rules, file, line, &errmsg);
4862 if (!chk) {
4863 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4864 goto error;
4865 }
4866 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004867 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004868
4869 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4870 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4871 if (!chk) {
4872 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4873 goto error;
4874 }
4875 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4876 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004877 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004878
4879 ruleset_found:
4880 rules->list = &rs->rules;
4881 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4882 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4883
4884 out:
4885 free(spop_req);
4886 free(errmsg);
4887 return err_code;
4888
4889 error:
4890 free_tcpcheck_ruleset(rs);
4891 err_code |= ERR_ALERT | ERR_FATAL;
4892 goto out;
4893}
4894
4895
4896static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4897{
4898 struct tcpcheck_rule *chk = NULL;
4899 struct tcpcheck_http_hdr *hdr = NULL;
4900 char *meth = NULL, *uri = NULL, *vsn = NULL;
4901 char *hdrs, *body;
4902
4903 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4904 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4905 if (hdrs == body)
4906 hdrs = NULL;
4907 if (hdrs) {
4908 *hdrs = '\0';
4909 hdrs +=2;
4910 }
4911 if (body) {
4912 *body = '\0';
4913 body += 4;
4914 }
4915 if (hdrs || body) {
4916 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4917 " Please, consider to use 'http-check send' directive instead.");
4918 }
4919
4920 chk = calloc(1, sizeof(*chk));
4921 if (!chk) {
4922 memprintf(errmsg, "out of memory");
4923 goto error;
4924 }
4925 chk->action = TCPCHK_ACT_SEND;
4926 chk->send.type = TCPCHK_SEND_HTTP;
4927 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4928 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4929 LIST_INIT(&chk->send.http.hdrs);
4930
4931 /* Copy the method, uri and version */
4932 if (*args[cur_arg]) {
4933 if (!*args[cur_arg+1])
4934 uri = args[cur_arg];
4935 else
4936 meth = args[cur_arg];
4937 }
4938 if (*args[cur_arg+1])
4939 uri = args[cur_arg+1];
4940 if (*args[cur_arg+2])
4941 vsn = args[cur_arg+2];
4942
4943 if (meth) {
4944 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4945 chk->send.http.meth.str.area = strdup(meth);
4946 chk->send.http.meth.str.data = strlen(meth);
4947 if (!chk->send.http.meth.str.area) {
4948 memprintf(errmsg, "out of memory");
4949 goto error;
4950 }
4951 }
4952 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004953 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004954 if (!isttest(chk->send.http.uri)) {
4955 memprintf(errmsg, "out of memory");
4956 goto error;
4957 }
4958 }
4959 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004960 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004961 if (!isttest(chk->send.http.vsn)) {
4962 memprintf(errmsg, "out of memory");
4963 goto error;
4964 }
4965 }
4966
4967 /* Copy the header */
4968 if (hdrs) {
4969 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4970 struct h1m h1m;
4971 int i, ret;
4972
4973 /* Build and parse the request */
4974 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4975
4976 h1m.flags = H1_MF_HDRS_ONLY;
4977 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4978 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4979 &h1m, NULL);
4980 if (ret <= 0) {
4981 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4982 goto error;
4983 }
4984
4985 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4986 hdr = calloc(1, sizeof(*hdr));
4987 if (!hdr) {
4988 memprintf(errmsg, "out of memory");
4989 goto error;
4990 }
4991 LIST_INIT(&hdr->value);
4992 hdr->name = istdup(tmp_hdrs[i].n);
4993 if (!hdr->name.ptr) {
4994 memprintf(errmsg, "out of memory");
4995 goto error;
4996 }
4997
4998 ist0(tmp_hdrs[i].v);
4999 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5000 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005001 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005002 }
5003 }
5004
5005 /* Copy the body */
5006 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005007 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005008 if (!isttest(chk->send.http.body)) {
5009 memprintf(errmsg, "out of memory");
5010 goto error;
5011 }
5012 }
5013
5014 return chk;
5015
5016 error:
5017 free_tcpcheck_http_hdr(hdr);
5018 free_tcpcheck(chk, 0);
5019 return NULL;
5020}
5021
5022/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005023int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005024 const char *file, int line)
5025{
5026 struct tcpcheck_ruleset *rs = NULL;
5027 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5028 struct tcpcheck_rule *chk;
5029 char *errmsg = NULL;
5030 int err_code = 0;
5031
5032 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5033 err_code |= ERR_WARN;
5034
5035 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5036 goto out;
5037
5038 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5039 if (!chk) {
5040 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5041 goto error;
5042 }
5043 if (errmsg) {
5044 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5045 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005046 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005047 }
5048
5049 no_request:
5050 curpx->options2 &= ~PR_O2_CHK_ANY;
5051 curpx->options2 |= PR_O2_TCPCHK_CHK;
5052
5053 free_tcpcheck_vars(&rules->preset_vars);
5054 rules->list = NULL;
5055 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5056
5057 /* Deduce the ruleset name from the proxy info */
5058 chunk_printf(&trash, "*http-check-%s_%s-%d",
5059 ((curpx == defpx) ? "defaults" : curpx->id),
5060 curpx->conf.file, curpx->conf.line);
5061
5062 rs = find_tcpcheck_ruleset(b_orig(&trash));
5063 if (rs == NULL) {
5064 rs = create_tcpcheck_ruleset(b_orig(&trash));
5065 if (rs == NULL) {
5066 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5067 goto error;
5068 }
5069 }
5070
5071 rules->list = &rs->rules;
5072 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5073 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5074 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5075 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5076 rules->list = NULL;
5077 goto error;
5078 }
5079
5080 out:
5081 free(errmsg);
5082 return err_code;
5083
5084 error:
5085 free_tcpcheck_ruleset(rs);
5086 free_tcpcheck(chk, 0);
5087 err_code |= ERR_ALERT | ERR_FATAL;
5088 goto out;
5089}
5090
5091/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005092int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005093 const char *file, int line)
5094{
5095 struct tcpcheck_ruleset *rs = NULL;
5096 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5097 int err_code = 0;
5098
5099 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5100 err_code |= ERR_WARN;
5101
5102 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5103 goto out;
5104
5105 curpx->options2 &= ~PR_O2_CHK_ANY;
5106 curpx->options2 |= PR_O2_TCPCHK_CHK;
5107
5108 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5109 /* If a tcp-check rulesset is already set, do nothing */
5110 if (rules->list)
5111 goto out;
5112
5113 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5114 * get it.
5115 */
5116 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5117 goto curpx_ruleset;
5118
5119 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5120 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5121 rs = find_tcpcheck_ruleset(b_orig(&trash));
5122 if (rs)
5123 goto ruleset_found;
5124 }
5125
5126 curpx_ruleset:
5127 /* Deduce the ruleset name from the proxy info */
5128 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5129 ((curpx == defpx) ? "defaults" : curpx->id),
5130 curpx->conf.file, curpx->conf.line);
5131
5132 rs = find_tcpcheck_ruleset(b_orig(&trash));
5133 if (rs == NULL) {
5134 rs = create_tcpcheck_ruleset(b_orig(&trash));
5135 if (rs == NULL) {
5136 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5137 goto error;
5138 }
5139 }
5140
5141 ruleset_found:
5142 free_tcpcheck_vars(&rules->preset_vars);
5143 rules->list = &rs->rules;
5144 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5145 rules->flags |= TCPCHK_RULES_TCP_CHK;
5146
5147 out:
5148 return err_code;
5149
5150 error:
5151 err_code |= ERR_ALERT | ERR_FATAL;
5152 goto out;
5153}
5154
Willy Tarreau51cd5952020-06-05 12:25:38 +02005155static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005156 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005157 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5158 { 0, NULL, NULL },
5159}};
5160
5161REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5162REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5163REGISTER_POST_DEINIT(deinit_tcpchecks);
5164INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);