blob: 587a609c5ce290be2b914b6b5e34a49944e1aecc [file] [log] [blame]
Willy Tarreau51cd5952020-06-05 12:25:38 +02001/*
2 * Health-checks functions.
3 *
4 * Copyright 2000-2009,2020 Willy Tarreau <w@1wt.eu>
5 * Copyright 2007-2010 Krzysztof Piotr Oledzki <ole@ans.pl>
6 * Copyright 2013 Baptiste Assmann <bedis9@gmail.com>
7 * Copyright 2020 Gaetan Rivet <grive@u256.net>
8 * Copyright 2020 Christopher Faulet <cfaulet@haproxy.com>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version
13 * 2 of the License, or (at your option) any later version.
14 *
15 */
16
17#include <sys/resource.h>
18#include <sys/socket.h>
19#include <sys/types.h>
20#include <sys/wait.h>
21#include <netinet/in.h>
22#include <netinet/tcp.h>
23#include <arpa/inet.h>
24
25#include <ctype.h>
26#include <errno.h>
27#include <fcntl.h>
28#include <signal.h>
29#include <stdarg.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <time.h>
34#include <unistd.h>
35
36#include <haproxy/action.h>
37#include <haproxy/api.h>
38#include <haproxy/cfgparse.h>
39#include <haproxy/check.h>
40#include <haproxy/chunk.h>
41#include <haproxy/connection.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020042#include <haproxy/errors.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020043#include <haproxy/global.h>
44#include <haproxy/h1.h>
45#include <haproxy/http.h>
46#include <haproxy/http_htx.h>
47#include <haproxy/htx.h>
48#include <haproxy/istbuf.h>
49#include <haproxy/list.h>
50#include <haproxy/log.h>
Christopher Faulet81011212021-09-16 16:01:09 +020051#include <haproxy/net_helper.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020052#include <haproxy/protocol.h>
53#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020054#include <haproxy/regex.h>
55#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020056#include <haproxy/server.h>
57#include <haproxy/ssl_sock.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020058#include <haproxy/task.h>
59#include <haproxy/tcpcheck.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020060#include <haproxy/time.h>
61#include <haproxy/tools.h>
Christopher Faulet147b8c92021-04-10 09:00:38 +020062#include <haproxy/trace.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020063#include <haproxy/vars.h>
64
65
Christopher Faulet147b8c92021-04-10 09:00:38 +020066#define TRACE_SOURCE &trace_check
67
Willy Tarreau51cd5952020-06-05 12:25:38 +020068/* Global tree to share all tcp-checks */
69struct eb_root shared_tcpchecks = EB_ROOT;
70
71
72DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
73
74/**************************************************************************/
75/*************** Init/deinit tcp-check rules and ruleset ******************/
76/**************************************************************************/
77/* Releases memory allocated for a log-format string */
78static void free_tcpcheck_fmt(struct list *fmt)
79{
80 struct logformat_node *lf, *lfb;
81
82 list_for_each_entry_safe(lf, lfb, fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +020083 LIST_DELETE(&lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +020084 release_sample_expr(lf->expr);
85 free(lf->arg);
86 free(lf);
87 }
88}
89
90/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
91void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
92{
93 if (!hdr)
94 return;
95
96 free_tcpcheck_fmt(&hdr->value);
97 istfree(&hdr->name);
98 free(hdr);
99}
100
101/* Releases memory allocated for an HTTP header list used in a tcp-check send
102 * rule
103 */
104static void free_tcpcheck_http_hdrs(struct list *hdrs)
105{
106 struct tcpcheck_http_hdr *hdr, *bhdr;
107
108 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200109 LIST_DELETE(&hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200110 free_tcpcheck_http_hdr(hdr);
111 }
112}
113
114/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
115 * tcp-check was allocated using a memory pool (it is used to instantiate email
116 * alerts).
117 */
118void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
119{
120 if (!rule)
121 return;
122
123 free(rule->comment);
124 switch (rule->action) {
125 case TCPCHK_ACT_SEND:
126 switch (rule->send.type) {
127 case TCPCHK_SEND_STRING:
128 case TCPCHK_SEND_BINARY:
129 istfree(&rule->send.data);
130 break;
131 case TCPCHK_SEND_STRING_LF:
132 case TCPCHK_SEND_BINARY_LF:
133 free_tcpcheck_fmt(&rule->send.fmt);
134 break;
135 case TCPCHK_SEND_HTTP:
136 free(rule->send.http.meth.str.area);
137 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
138 istfree(&rule->send.http.uri);
139 else
140 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
141 istfree(&rule->send.http.vsn);
142 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
143 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
144 istfree(&rule->send.http.body);
145 else
146 free_tcpcheck_fmt(&rule->send.http.body_fmt);
147 break;
148 case TCPCHK_SEND_UNDEF:
149 break;
150 }
151 break;
152 case TCPCHK_ACT_EXPECT:
153 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
154 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
155 release_sample_expr(rule->expect.status_expr);
156 switch (rule->expect.type) {
157 case TCPCHK_EXPECT_HTTP_STATUS:
158 free(rule->expect.codes.codes);
159 break;
160 case TCPCHK_EXPECT_STRING:
161 case TCPCHK_EXPECT_BINARY:
162 case TCPCHK_EXPECT_HTTP_BODY:
163 istfree(&rule->expect.data);
164 break;
165 case TCPCHK_EXPECT_STRING_REGEX:
166 case TCPCHK_EXPECT_BINARY_REGEX:
167 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
168 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
169 regex_free(rule->expect.regex);
170 break;
171 case TCPCHK_EXPECT_STRING_LF:
172 case TCPCHK_EXPECT_BINARY_LF:
173 case TCPCHK_EXPECT_HTTP_BODY_LF:
174 free_tcpcheck_fmt(&rule->expect.fmt);
175 break;
176 case TCPCHK_EXPECT_HTTP_HEADER:
177 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
178 regex_free(rule->expect.hdr.name_re);
179 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
180 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
181 else
182 istfree(&rule->expect.hdr.name);
183
184 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
185 regex_free(rule->expect.hdr.value_re);
186 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
187 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
188 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
189 istfree(&rule->expect.hdr.value);
190 break;
191 case TCPCHK_EXPECT_CUSTOM:
192 case TCPCHK_EXPECT_UNDEF:
193 break;
194 }
195 break;
196 case TCPCHK_ACT_CONNECT:
197 free(rule->connect.sni);
198 free(rule->connect.alpn);
199 release_sample_expr(rule->connect.port_expr);
200 break;
201 case TCPCHK_ACT_COMMENT:
202 break;
203 case TCPCHK_ACT_ACTION_KW:
204 free(rule->action_kw.rule);
205 break;
206 }
207
208 if (in_pool)
209 pool_free(pool_head_tcpcheck_rule, rule);
210 else
211 free(rule);
212}
213
214/* Creates a tcp-check variable used in preset variables before executing a
215 * tcp-check ruleset.
216 */
217struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
218{
219 struct tcpcheck_var *var = NULL;
220
221 var = calloc(1, sizeof(*var));
222 if (var == NULL)
223 return NULL;
224
225 var->name = istdup(name);
226 if (!isttest(var->name)) {
227 free(var);
228 return NULL;
229 }
230
231 LIST_INIT(&var->list);
232 return var;
233}
234
235/* Releases memory allocated for a preset tcp-check variable */
236void free_tcpcheck_var(struct tcpcheck_var *var)
237{
238 if (!var)
239 return;
240
241 istfree(&var->name);
242 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
243 free(var->data.u.str.area);
244 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
245 free(var->data.u.meth.str.area);
246 free(var);
247}
248
249/* Releases a list of preset tcp-check variables */
250void free_tcpcheck_vars(struct list *vars)
251{
252 struct tcpcheck_var *var, *back;
253
254 list_for_each_entry_safe(var, back, vars, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200255 LIST_DELETE(&var->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200256 free_tcpcheck_var(var);
257 }
258}
259
260/* Duplicate a list of preset tcp-check variables */
Willy Tarreau09f2e772021-02-12 08:42:30 +0100261int dup_tcpcheck_vars(struct list *dst, const struct list *src)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200262{
Willy Tarreau09f2e772021-02-12 08:42:30 +0100263 const struct tcpcheck_var *var;
264 struct tcpcheck_var *new = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200265
266 list_for_each_entry(var, src, list) {
267 new = create_tcpcheck_var(var->name);
268 if (!new)
269 goto error;
270 new->data.type = var->data.type;
271 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
272 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
273 goto error;
274 if (var->data.type == SMP_T_STR)
275 new->data.u.str.area[new->data.u.str.data] = 0;
276 }
277 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
278 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
279 goto error;
280 new->data.u.str.area[new->data.u.str.data] = 0;
281 new->data.u.meth.meth = var->data.u.meth.meth;
282 }
283 else
284 new->data.u = var->data.u;
Willy Tarreau2b718102021-04-21 07:32:39 +0200285 LIST_APPEND(dst, &new->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200286 }
287 return 1;
288
289 error:
290 free(new);
291 return 0;
292}
293
294/* Looks for a shared tcp-check ruleset given its name. */
295struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
296{
297 struct tcpcheck_ruleset *rs;
298 struct ebpt_node *node;
299
300 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
301 if (node) {
302 rs = container_of(node, typeof(*rs), node);
303 return rs;
304 }
305 return NULL;
306}
307
308/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks
309 * tree.
310 */
311struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
312{
313 struct tcpcheck_ruleset *rs;
314
315 rs = calloc(1, sizeof(*rs));
316 if (rs == NULL)
317 return NULL;
318
319 rs->node.key = strdup(name);
320 if (rs->node.key == NULL) {
321 free(rs);
322 return NULL;
323 }
324
325 LIST_INIT(&rs->rules);
326 ebis_insert(&shared_tcpchecks, &rs->node);
327 return rs;
328}
329
330/* Releases memory allocated by a tcp-check ruleset. */
331void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
332{
333 struct tcpcheck_rule *r, *rb;
334
335 if (!rs)
336 return;
337
338 ebpt_delete(&rs->node);
339 free(rs->node.key);
340 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200341 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200342 free_tcpcheck(r, 0);
343 }
344 free(rs);
345}
346
347
348/**************************************************************************/
349/**************** Everything about tcp-checks execution *******************/
350/**************************************************************************/
351/* Returns the id of a step in a tcp-check ruleset */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200352int tcpcheck_get_step_id(const struct check *check, const struct tcpcheck_rule *rule)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200353{
354 if (!rule)
355 rule = check->current_step;
356
357 /* no last started step => first step */
358 if (!rule)
359 return 1;
360
361 /* last step is the first implicit connect */
362 if (rule->index == 0 &&
363 rule->action == TCPCHK_ACT_CONNECT &&
364 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
365 return 0;
366
367 return rule->index + 1;
368}
369
370/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
371 * NULL if none was found.
372 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200373struct tcpcheck_rule *get_first_tcpcheck_rule(const struct tcpcheck_rules *rules)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200374{
375 struct tcpcheck_rule *r;
376
377 list_for_each_entry(r, rules->list, list) {
378 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
379 return r;
380 }
381 return NULL;
382}
383
384/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
385 * NULL if none was found.
386 */
387static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
388{
389 struct tcpcheck_rule *r;
390
391 list_for_each_entry_rev(r, rules->list, list) {
392 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
393 return r;
394 }
395 return NULL;
396}
397
398/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
399 * <start> or NULL if non was found. If <start> is NULL, it relies on
400 * get_first_tcpcheck_rule().
401 */
402static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
403{
404 struct tcpcheck_rule *r;
405
406 if (!start)
407 return get_first_tcpcheck_rule(rules);
408
409 r = LIST_NEXT(&start->list, typeof(r), list);
410 list_for_each_entry_from(r, rules->list, list) {
411 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
412 return r;
413 }
414 return NULL;
415}
416
417
418/* Creates info message when a tcp-check healthcheck fails on an expect rule */
419static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
420 int match, struct ist info)
421{
422 struct sample *smp;
423
424 /* Follows these step to produce the info message:
425 * 1. if info field is already provided, copy it
426 * 2. if the expect rule provides an onerror log-format string,
427 * use it to produce the message
428 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
429 * 4. Otherwise produce the generic tcp-check info message
430 */
431 if (istlen(info)) {
432 chunk_strncat(msg, istptr(info), istlen(info));
433 goto comment;
434 }
435 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
436 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
437 goto comment;
438 }
439
440 if (check->type == PR_O2_TCPCHK_CHK &&
441 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
442 goto comment;
443
444 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
445 switch (rule->expect.type) {
446 case TCPCHK_EXPECT_HTTP_STATUS:
447 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
448 break;
449 case TCPCHK_EXPECT_STRING:
450 case TCPCHK_EXPECT_HTTP_BODY:
451 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
452 tcpcheck_get_step_id(check, rule));
453 break;
454 case TCPCHK_EXPECT_BINARY:
455 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
456 break;
457 case TCPCHK_EXPECT_STRING_REGEX:
458 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
459 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
460 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
461 break;
462 case TCPCHK_EXPECT_BINARY_REGEX:
463 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
464 break;
465 case TCPCHK_EXPECT_STRING_LF:
466 case TCPCHK_EXPECT_HTTP_BODY_LF:
467 chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule));
468 break;
469 case TCPCHK_EXPECT_BINARY_LF:
470 chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule));
471 break;
472 case TCPCHK_EXPECT_CUSTOM:
473 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
474 break;
475 case TCPCHK_EXPECT_HTTP_HEADER:
476 chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
477 case TCPCHK_EXPECT_UNDEF:
478 /* Should never happen. */
479 return;
480 }
481
482 comment:
483 /* If the failing expect rule provides a comment, it is concatenated to
484 * the info message.
485 */
486 if (rule->comment) {
487 chunk_strcat(msg, " comment: ");
488 chunk_strcat(msg, rule->comment);
489 }
490
491 /* Finally, the check status code is set if the failing expect rule
492 * defines a status expression.
493 */
494 if (rule->expect.status_expr) {
495 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
496 rule->expect.status_expr, SMP_T_STR);
497
498 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
499 sample_casts[smp->data.type][SMP_T_SINT](smp))
500 check->code = smp->data.u.sint;
501 }
502
503 *(b_tail(msg)) = '\0';
504}
505
506/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
507static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
508 struct ist info)
509{
510 struct sample *smp;
511
512 /* Follows these step to produce the info message:
513 * 1. if info field is already provided, copy it
514 * 2. if the expect rule provides an onsucces log-format string,
515 * use it to produce the message
516 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
517 * 4. Otherwise produce the generic tcp-check info message
518 */
519 if (istlen(info))
520 chunk_strncat(msg, istptr(info), istlen(info));
521 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
522 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
523 &rule->expect.onsuccess_fmt);
524 else if (check->type == PR_O2_TCPCHK_CHK &&
525 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
526 chunk_strcat(msg, "(tcp-check)");
527
528 /* Finally, the check status code is set if the expect rule defines a
529 * status expression.
530 */
531 if (rule->expect.status_expr) {
532 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
533 rule->expect.status_expr, SMP_T_STR);
534
535 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
536 sample_casts[smp->data.type][SMP_T_SINT](smp))
537 check->code = smp->data.u.sint;
538 }
539
540 *(b_tail(msg)) = '\0';
541}
542
543/* Internal functions to parse and validate a MySQL packet in the context of an
544 * expect rule. It start to parse the input buffer at the offset <offset>. If
545 * <last_read> is set, no more data are expected.
546 */
547static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
548 unsigned int offset, int last_read)
549{
550 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
551 enum healthcheck_status status;
552 struct buffer *msg = NULL;
553 struct ist desc = IST_NULL;
554 unsigned int err = 0, plen = 0;
555
556
Christopher Faulet147b8c92021-04-10 09:00:38 +0200557 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
558
Willy Tarreau51cd5952020-06-05 12:25:38 +0200559 /* 3 Bytes for the packet length and 1 byte for the sequence id */
560 if (b_data(&check->bi) < offset+4) {
561 if (!last_read)
562 goto wait_more_data;
563
564 /* invalid length or truncated response */
565 status = HCHK_STATUS_L7RSP;
566 goto error;
567 }
568
569 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
570 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
571 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
572
573 if (b_data(&check->bi) < offset+plen+4) {
574 if (!last_read)
575 goto wait_more_data;
576
577 /* invalid length or truncated response */
578 status = HCHK_STATUS_L7RSP;
579 goto error;
580 }
581
582 if (*b_peek(&check->bi, offset+4) == '\xff') {
583 /* MySQL Error packet always begin with field_count = 0xff */
584 status = HCHK_STATUS_L7STS;
585 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
586 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
587 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
588 goto error;
589 }
590
591 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
592 /* Not the last rule, continue */
593 goto out;
594 }
595
596 /* We set the MySQL Version in description for information purpose
597 * FIXME : it can be cool to use MySQL Version for other purpose,
598 * like mark as down old MySQL server.
599 */
600 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
601 set_server_check_status(check, status, b_peek(&check->bi, 5));
602
603 out:
604 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200605 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200606 return ret;
607
608 error:
609 ret = TCPCHK_EVAL_STOP;
610 check->code = err;
611 msg = alloc_trash_chunk();
612 if (msg)
613 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
614 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
615 goto out;
616
617 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200618 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200619 ret = TCPCHK_EVAL_WAIT;
620 goto out;
621}
622
623/* Custom tcp-check expect function to parse and validate the MySQL initial
624 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
625 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
626 * error occurred.
627 */
628enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
629{
630 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
631}
632
633/* Custom tcp-check expect function to parse and validate the MySQL OK packet
634 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
635 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
636 * an error occurred.
637 */
638enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
639{
640 unsigned int hslen = 0;
641
642 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
643 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
644 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
645
646 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
647}
648
649/* Custom tcp-check expect function to parse and validate the LDAP bind response
650 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
651 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
652 * error occurred.
653 */
654enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
655{
656 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
657 enum healthcheck_status status;
658 struct buffer *msg = NULL;
659 struct ist desc = IST_NULL;
Christopher Faulet81011212021-09-16 16:01:09 +0200660 char *ptr;
661 unsigned short nbytes = 0;
662 size_t msglen = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200663
Christopher Faulet147b8c92021-04-10 09:00:38 +0200664 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
665
Willy Tarreau51cd5952020-06-05 12:25:38 +0200666 /* Check if the server speaks LDAP (ASN.1/BER)
667 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
668 * http://tools.ietf.org/html/rfc4511
669 */
Christopher Faulet81011212021-09-16 16:01:09 +0200670 ptr = b_head(&check->bi) + 1;
671
Willy Tarreau51cd5952020-06-05 12:25:38 +0200672 /* size of LDAPMessage */
Christopher Faulet81011212021-09-16 16:01:09 +0200673 if (*ptr & 0x80) {
674 /* For message size encoded on several bytes, we only handle
675 * size encoded on 2 or 4 bytes. There is no reason to make this
676 * part to complex because only Active Directory is known to
677 * encode BindReponse length on 4 bytes.
678 */
679 nbytes = (*ptr & 0x7f);
680 if (b_data(&check->bi) < 1 + nbytes)
681 goto too_short;
682 switch (nbytes) {
683 case 4: msglen = read_n32(ptr+1); break;
684 case 2: msglen = read_n16(ptr+1); break;
685 default:
686 status = HCHK_STATUS_L7RSP;
687 desc = ist("Not LDAPv3 protocol");
688 goto error;
689 }
690 }
691 else
692 msglen = *ptr;
693 ptr += 1 + nbytes;
694
695 if (b_data(&check->bi) < 2 + nbytes + msglen)
696 goto too_short;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200697
698 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
699 * messageID: 0x02 0x01 0x01: INTEGER 1
700 * protocolOp: 0x61: bindResponse
701 */
Christopher Faulet81011212021-09-16 16:01:09 +0200702 if (memcmp(ptr, "\x02\x01\x01\x61", 4) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200703 status = HCHK_STATUS_L7RSP;
704 desc = ist("Not LDAPv3 protocol");
705 goto error;
706 }
Christopher Faulet81011212021-09-16 16:01:09 +0200707 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200708
Christopher Faulet81011212021-09-16 16:01:09 +0200709 /* skip size of bindResponse */
710 nbytes = 0;
711 if (*ptr & 0x80)
712 nbytes = (*ptr & 0x7f);
713 ptr += 1 + nbytes;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200714
715 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
716 * ldapResult: 0x0a 0x01: ENUMERATION
717 */
Christopher Faulet81011212021-09-16 16:01:09 +0200718 if (memcmp(ptr, "\x0a\x01", 2) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200719 status = HCHK_STATUS_L7RSP;
720 desc = ist("Not LDAPv3 protocol");
721 goto error;
722 }
Christopher Faulet81011212021-09-16 16:01:09 +0200723 ptr += 2;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200724
725 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
726 * resultCode
727 */
Christopher Faulet81011212021-09-16 16:01:09 +0200728 check->code = *ptr;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200729 if (check->code) {
730 status = HCHK_STATUS_L7STS;
731 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
732 goto error;
733 }
734
735 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
736 set_server_check_status(check, status, "Success");
737
738 out:
739 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200740 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200741 return ret;
742
743 error:
744 ret = TCPCHK_EVAL_STOP;
745 msg = alloc_trash_chunk();
746 if (msg)
747 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
748 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
749 goto out;
Christopher Faulet81011212021-09-16 16:01:09 +0200750
751 too_short:
752 if (!last_read)
753 goto wait_more_data;
754 /* invalid length or truncated response */
755 status = HCHK_STATUS_L7RSP;
756 goto error;
757
758 wait_more_data:
759 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
760 ret = TCPCHK_EVAL_WAIT;
761 goto out;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200762}
763
764/* Custom tcp-check expect function to parse and validate the SPOP hello agent
765 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
766 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
767 */
768enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
769{
770 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
771 enum healthcheck_status status;
772 struct buffer *msg = NULL;
773 struct ist desc = IST_NULL;
774 unsigned int framesz;
775
Christopher Faulet147b8c92021-04-10 09:00:38 +0200776 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200777
778 memcpy(&framesz, b_head(&check->bi), 4);
779 framesz = ntohl(framesz);
780
781 if (!last_read && b_data(&check->bi) < (4+framesz))
782 goto wait_more_data;
783
784 memset(b_orig(&trash), 0, b_size(&trash));
785 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
786 status = HCHK_STATUS_L7RSP;
787 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
788 goto error;
789 }
790
791 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
792 set_server_check_status(check, status, "SPOA server is ok");
793
794 out:
795 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200796 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200797 return ret;
798
799 error:
800 ret = TCPCHK_EVAL_STOP;
801 msg = alloc_trash_chunk();
802 if (msg)
803 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
804 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
805 goto out;
806
807 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200808 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200809 ret = TCPCHK_EVAL_WAIT;
810 goto out;
811}
812
813/* Custom tcp-check expect function to parse and validate the agent-check
814 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
815 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
816 */
817enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
818{
819 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
820 enum healthcheck_status status = HCHK_STATUS_CHECKED;
821 const char *hs = NULL; /* health status */
822 const char *as = NULL; /* admin status */
823 const char *ps = NULL; /* performance status */
824 const char *cs = NULL; /* maxconn */
825 const char *err = NULL; /* first error to report */
826 const char *wrn = NULL; /* first warning to report */
827 char *cmd, *p;
828
Christopher Faulet147b8c92021-04-10 09:00:38 +0200829 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
830
Willy Tarreau51cd5952020-06-05 12:25:38 +0200831 /* We're getting an agent check response. The agent could
832 * have been disabled in the mean time with a long check
833 * still pending. It is important that we ignore the whole
834 * response.
835 */
836 if (!(check->state & CHK_ST_ENABLED))
837 goto out;
838
839 /* The agent supports strings made of a single line ended by the
840 * first CR ('\r') or LF ('\n'). This line is composed of words
841 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
842 * line may optionally contained a description of a state change
843 * after a sharp ('#'), which is only considered if a health state
844 * is announced.
845 *
846 * Words may be composed of :
847 * - a numeric weight suffixed by the percent character ('%').
848 * - a health status among "up", "down", "stopped", and "fail".
849 * - an admin status among "ready", "drain", "maint".
850 *
851 * These words may appear in any order. If multiple words of the
852 * same category appear, the last one wins.
853 */
854
855 p = b_head(&check->bi);
856 while (*p && *p != '\n' && *p != '\r')
857 p++;
858
859 if (!*p) {
860 if (!last_read)
861 goto wait_more_data;
862
863 /* at least inform the admin that the agent is mis-behaving */
864 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
865 goto out;
866 }
867
868 *p = 0;
869 cmd = b_head(&check->bi);
870
871 while (*cmd) {
872 /* look for next word */
873 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
874 cmd++;
875 continue;
876 }
877
878 if (*cmd == '#') {
879 /* this is the beginning of a health status description,
880 * skip the sharp and blanks.
881 */
882 cmd++;
883 while (*cmd == '\t' || *cmd == ' ')
884 cmd++;
885 break;
886 }
887
888 /* find the end of the word so that we have a null-terminated
889 * word between <cmd> and <p>.
890 */
891 p = cmd + 1;
892 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
893 p++;
894 if (*p)
895 *p++ = 0;
896
897 /* first, health statuses */
898 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100899 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200900 status = HCHK_STATUS_L7OKD;
901 hs = cmd;
902 }
903 else if (strcasecmp(cmd, "down") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100904 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200905 status = HCHK_STATUS_L7STS;
906 hs = cmd;
907 }
908 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100909 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200910 status = HCHK_STATUS_L7STS;
911 hs = cmd;
912 }
913 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100914 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200915 status = HCHK_STATUS_L7STS;
916 hs = cmd;
917 }
918 /* admin statuses */
919 else if (strcasecmp(cmd, "ready") == 0) {
920 as = cmd;
921 }
922 else if (strcasecmp(cmd, "drain") == 0) {
923 as = cmd;
924 }
925 else if (strcasecmp(cmd, "maint") == 0) {
926 as = cmd;
927 }
928 /* try to parse a weight here and keep the last one */
929 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
930 ps = cmd;
931 }
932 /* try to parse a maxconn here */
933 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
934 cs = cmd;
935 }
936 else {
937 /* keep a copy of the first error */
938 if (!err)
939 err = cmd;
940 }
941 /* skip to next word */
942 cmd = p;
943 }
944 /* here, cmd points either to \0 or to the beginning of a
945 * description. Skip possible leading spaces.
946 */
947 while (*cmd == ' ' || *cmd == '\n')
948 cmd++;
949
950 /* First, update the admin status so that we avoid sending other
951 * possibly useless warnings and can also update the health if
952 * present after going back up.
953 */
954 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200955 if (strcasecmp(as, "drain") == 0) {
956 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200957 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200958 }
959 else if (strcasecmp(as, "maint") == 0) {
960 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200961 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200962 }
963 else {
964 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200965 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200966 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200967 }
968
969 /* now change weights */
970 if (ps) {
971 const char *msg;
972
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500973 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200974 msg = server_parse_weight_change_request(check->server, ps);
975 if (!wrn || !*wrn)
976 wrn = msg;
977 }
978
979 if (cs) {
980 const char *msg;
981
982 cs += strlen("maxconn:");
983
Christopher Faulet147b8c92021-04-10 09:00:38 +0200984 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury Denoyellecaaafd02021-06-18 11:11:36 +0200985 /* This is safe to call server_parse_maxconn_change_request
986 * because the server lock is held during the check.
987 */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200988 msg = server_parse_maxconn_change_request(check->server, cs);
989 if (!wrn || !*wrn)
990 wrn = msg;
991 }
992
993 /* and finally health status */
994 if (hs) {
995 /* We'll report some of the warnings and errors we have
996 * here. Down reports are critical, we leave them untouched.
997 * Lack of report, or report of 'UP' leaves the room for
998 * ERR first, then WARN.
999 */
1000 const char *msg = cmd;
1001 struct buffer *t;
1002
1003 if (!*msg || status == HCHK_STATUS_L7OKD) {
1004 if (err && *err)
1005 msg = err;
1006 else if (wrn && *wrn)
1007 msg = wrn;
1008 }
1009
1010 t = get_trash_chunk();
1011 chunk_printf(t, "via agent : %s%s%s%s",
1012 hs, *msg ? " (" : "",
1013 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001014 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001015 set_server_check_status(check, status, t->area);
1016 }
1017 else if (err && *err) {
1018 /* No status change but we'd like to report something odd.
1019 * Just report the current state and copy the message.
1020 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001021 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001022 chunk_printf(&trash, "agent reports an error : %s", err);
1023 set_server_check_status(check, status/*check->status*/, trash.area);
1024 }
1025 else if (wrn && *wrn) {
1026 /* No status change but we'd like to report something odd.
1027 * Just report the current state and copy the message.
1028 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001029 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001030 chunk_printf(&trash, "agent warns : %s", wrn);
1031 set_server_check_status(check, status/*check->status*/, trash.area);
1032 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001033 else {
1034 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001035 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001036 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001037
1038 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001039 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001040 return ret;
1041
1042 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001043 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001044 ret = TCPCHK_EVAL_WAIT;
1045 goto out;
1046}
1047
1048/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1049 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1050 * TCPCHK_EVAL_STOP if an error occurred.
1051 */
1052enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1053{
1054 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1055 struct tcpcheck_connect *connect = &rule->connect;
1056 struct proxy *proxy = check->proxy;
1057 struct server *s = check->server;
1058 struct task *t = check->task;
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001059 struct conn_stream *cs = check->cs;
1060 struct connection *conn = cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001061 struct protocol *proto;
1062 struct xprt_ops *xprt;
1063 struct tcpcheck_rule *next;
1064 int status, port;
1065
Christopher Faulet147b8c92021-04-10 09:00:38 +02001066 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1067
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001068 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1069
1070 /* current connection already created, check if it is established or not */
1071 if (conn) {
1072 if (conn->flags & CO_FL_WAIT_XPRT) {
1073 /* We are still waiting for the connection establishment */
1074 if (next && next->action == TCPCHK_ACT_SEND) {
1075 if (!(check->wait_list.events & SUB_RETRY_SEND))
1076 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1077 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001078 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001079 }
1080 else
1081 ret = tcpcheck_eval_recv(check, rule);
1082 }
1083 goto out;
1084 }
1085
1086 /* Note: here check->cs = cs = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001087
Christopher Fauletb381a502020-11-25 13:47:00 +01001088 /* Always release input and output buffer when a new connect is evaluated */
1089 check_release_buf(check, &check->bi);
1090 check_release_buf(check, &check->bo);
1091
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001092 /* No connection, prepare a new one */
Christopher Faulet236c93b2020-07-02 09:19:54 +02001093 cs = cs_new(NULL, (s ? &s->obj_type : &proxy->obj_type));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001094 if (!cs) {
1095 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1096 tcpcheck_get_step_id(check, rule));
1097 if (rule->comment)
1098 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1099 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1100 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001101 TRACE_ERROR("conn-stream allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001102 goto out;
1103 }
1104
Willy Tarreau51cd5952020-06-05 12:25:38 +02001105 tasklet_set_tid(check->wait_list.tasklet, tid);
1106
1107 check->cs = cs;
1108 conn = cs->conn;
1109 conn_set_owner(conn, check->sess, NULL);
1110
1111 /* Maybe there were an older connection we were waiting on */
1112 check->wait_list.events = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001113
1114 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001115 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001116 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001117 status = SF_ERR_RESOURCE;
1118 goto fail_check;
1119 }
1120
1121 /* connect to the connect rule addr if specified, otherwise the check
1122 * addr if specified on the server. otherwise, use the server addr (it
1123 * MUST exist at this step).
1124 */
1125 *conn->dst = (is_addr(&connect->addr)
1126 ? connect->addr
1127 : (is_addr(&check->addr) ? check->addr : s->addr));
1128 proto = protocol_by_family(conn->dst->ss_family);
1129
1130 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001131 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001132 port = connect->port;
1133 if (!port && connect->port_expr) {
1134 struct sample *smp;
1135
1136 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1137 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1138 connect->port_expr, SMP_T_SINT);
1139 if (smp)
1140 port = smp->data.u.sint;
1141 }
1142 if (!port && is_inet_addr(&connect->addr))
1143 port = get_host_port(&connect->addr);
1144 if (!port && check->port)
1145 port = check->port;
1146 if (!port && is_inet_addr(&check->addr))
1147 port = get_host_port(&check->addr);
1148 if (!port) {
1149 /* The server MUST exist here */
1150 port = s->svc_port;
1151 }
1152 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001153 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001154
1155 xprt = ((connect->options & TCPCHK_OPT_SSL)
1156 ? xprt_get(XPRT_SSL)
1157 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1158
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001159 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001160 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001161 status = SF_ERR_RESOURCE;
1162 goto fail_check;
1163 }
1164
Willy Tarreau51cd5952020-06-05 12:25:38 +02001165 cs_attach(cs, check, &check_conn_cb);
1166
Christopher Fauletf7177272020-10-02 13:41:55 +02001167 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1168 conn->send_proxy_ofs = 1;
1169 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001170 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001171 }
1172 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1173 conn->send_proxy_ofs = 1;
1174 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001175 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001176 }
1177
1178 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1179 conn->send_proxy_ofs = 1;
1180 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001181 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001182 }
1183 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1184 conn->send_proxy_ofs = 1;
1185 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001186 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001187 }
1188
Willy Tarreau51cd5952020-06-05 12:25:38 +02001189 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001190 if (proto && proto->connect) {
1191 int flags = 0;
1192
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 Faulet18280ca2021-08-11 15:46:29 +02001352 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001353 TRACE_DEVEL("Data still pending, try to send it now", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Christopher Faulet39066c22020-11-25 13:34:51 +01001354 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001355 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001356
Christopher Fauletb381a502020-11-25 13:47:00 +01001357 /* Always release input buffer when a new send is evaluated */
1358 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001359
1360 switch (send->type) {
1361 case TCPCHK_SEND_STRING:
1362 case TCPCHK_SEND_BINARY:
1363 if (istlen(send->data) >= b_size(&check->bo)) {
1364 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1365 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1366 tcpcheck_get_step_id(check, rule));
1367 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1368 ret = TCPCHK_EVAL_STOP;
1369 goto out;
1370 }
1371 b_putist(&check->bo, send->data);
1372 break;
1373 case TCPCHK_SEND_STRING_LF:
1374 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1375 if (!b_data(&check->bo))
1376 goto out;
1377 break;
1378 case TCPCHK_SEND_BINARY_LF: {
1379 int len = b_size(&check->bo);
1380
1381 tmp = alloc_trash_chunk();
1382 if (!tmp)
1383 goto error_lf;
1384 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1385 if (!b_data(tmp))
1386 goto out;
1387 tmp->area[tmp->data] = '\0';
1388 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1389 goto error_lf;
1390 check->bo.data = len;
1391 break;
1392 }
1393 case TCPCHK_SEND_HTTP: {
1394 struct htx_sl *sl;
1395 struct ist meth, uri, vsn, clen, body;
1396 unsigned int slflags = 0;
1397
1398 tmp = alloc_trash_chunk();
1399 if (!tmp)
1400 goto error_htx;
1401
1402 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1403 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1404 : http_known_methods[send->http.meth.meth]);
1405 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1406 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1407 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1408 }
1409 else
1410 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1411 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1412
1413 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1414 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1415 slflags |= HTX_SL_F_VER_11;
1416 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1417 if (!isttest(send->http.body))
1418 slflags |= HTX_SL_F_BODYLESS;
1419
1420 htx = htx_from_buf(&check->bo);
1421 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1422 if (!sl)
1423 goto error_htx;
1424 sl->info.req.meth = send->http.meth.meth;
1425 if (!http_update_host(htx, sl, uri))
1426 goto error_htx;
1427
1428 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1429 struct tcpcheck_http_hdr *hdr;
1430 struct ist hdr_value;
1431
1432 list_for_each_entry(hdr, &send->http.hdrs, list) {
1433 chunk_reset(tmp);
1434 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1435 if (!b_data(tmp))
1436 continue;
1437 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1438 if (!htx_add_header(htx, hdr->name, hdr_value))
1439 goto error_htx;
1440 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1441 if (!http_update_authority(htx, sl, hdr_value))
1442 goto error_htx;
1443 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001444 if (isteqi(hdr->name, ist("connection")))
1445 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001446 }
1447
1448 }
1449 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1450 chunk_reset(tmp);
1451 httpchk_build_status_header(check->server, tmp);
1452 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1453 goto error_htx;
1454 }
1455
1456
1457 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1458 chunk_reset(tmp);
1459 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1460 body = ist2(b_orig(tmp), b_data(tmp));
1461 }
1462 else
1463 body = send->http.body;
1464 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1465
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001466 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001467 !htx_add_header(htx, ist("Content-length"), clen))
1468 goto error_htx;
1469
1470
1471 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001472 (istlen(body) && !htx_add_data_atonce(htx, body)))
1473 goto error_htx;
1474
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001475 /* no more data are expected */
1476 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001477 htx_to_buf(htx, &check->bo);
1478 break;
1479 }
1480 case TCPCHK_SEND_UNDEF:
1481 /* Should never happen. */
1482 ret = TCPCHK_EVAL_STOP;
1483 goto out;
1484 };
1485
Christopher Faulet39066c22020-11-25 13:34:51 +01001486 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001487 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001488 if (conn->mux->snd_buf(cs, &check->bo,
1489 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1490 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1491 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001492 TRACE_DEVEL("connection error during send", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001493 goto out;
1494 }
1495 }
Christopher Faulet18280ca2021-08-11 15:46:29 +02001496 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001497 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1498 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001499 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001500 goto out;
1501 }
1502
1503 out:
1504 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001505 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1506 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001507
1508 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001509 return ret;
1510
1511 error_htx:
1512 if (htx) {
1513 htx_reset(htx);
1514 htx_to_buf(htx, &check->bo);
1515 }
1516 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1517 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001518 TRACE_ERROR("failed to build HTTP request", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001519 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1520 ret = TCPCHK_EVAL_STOP;
1521 goto out;
1522
1523 error_lf:
1524 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1525 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001526 TRACE_ERROR("failed to build log-format string", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001527 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1528 ret = TCPCHK_EVAL_STOP;
1529 goto out;
1530
1531}
1532
1533/* Try to receive data before evaluating a tcp-check expect rule. Returns
1534 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1535 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1536 * TCPCHK_EVAL_STOP if an error occurred.
1537 */
1538enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1539{
1540 struct conn_stream *cs = check->cs;
1541 struct connection *conn = cs_conn(cs);
1542 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1543 size_t max, read, cur_read = 0;
1544 int is_empty;
1545 int read_poll = MAX_READ_POLL_LOOPS;
1546
Christopher Faulet147b8c92021-04-10 09:00:38 +02001547 TRACE_ENTER(CHK_EV_RX_DATA, check);
1548
1549 if (check->wait_list.events & SUB_RETRY_RECV) {
1550 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001551 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001552 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001553
1554 if (cs->flags & CS_FL_EOS)
1555 goto end_recv;
1556
Christopher Faulet147b8c92021-04-10 09:00:38 +02001557 if (check->state & CHK_ST_IN_ALLOC) {
1558 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001559 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001560 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001561
1562 if (!check_get_buf(check, &check->bi)) {
1563 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001564 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001565 goto wait_more_data;
1566 }
1567
Willy Tarreau51cd5952020-06-05 12:25:38 +02001568 /* errors on the connection and the conn-stream were already checked */
1569
1570 /* prepare to detect if the mux needs more room */
1571 cs->flags &= ~CS_FL_WANT_ROOM;
1572
1573 while ((cs->flags & CS_FL_RCV_MORE) ||
1574 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1575 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1576 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1577 cur_read += read;
1578 if (!read ||
1579 (cs->flags & CS_FL_WANT_ROOM) ||
1580 (--read_poll <= 0) ||
1581 (read < max && read >= global.tune.recv_enough))
1582 break;
1583 }
1584
1585 end_recv:
1586 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1587 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1588 /* Report network errors only if we got no other data. Otherwise
1589 * we'll let the upper layers decide whether the response is OK
1590 * or not. It is very common that an RST sent by the server is
1591 * reported as an error just after the last data chunk.
1592 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001593 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001594 goto stop;
1595 }
1596 if (!cur_read) {
Christopher Fauletbd017472021-10-20 13:53:38 +02001597 if (cs->flags & CS_FL_EOI) {
1598 /* If EOI is set, it means there is a response or an error */
1599 goto out;
1600 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001601 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1602 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001603 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001604 goto wait_more_data;
1605 }
1606 if (is_empty) {
1607 int status;
1608
1609 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1610 tcpcheck_get_step_id(check, rule));
1611 if (rule->comment)
1612 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1613
Christopher Faulet147b8c92021-04-10 09:00:38 +02001614 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001615 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1616 set_server_check_status(check, status, trash.area);
1617 goto stop;
1618 }
1619 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001620 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001621
1622 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001623 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1624 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001625
1626 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001627 return ret;
1628
1629 stop:
1630 ret = TCPCHK_EVAL_STOP;
1631 goto out;
1632
1633 wait_more_data:
1634 ret = TCPCHK_EVAL_WAIT;
1635 goto out;
1636}
1637
1638/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1639 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1640 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1641 * error occurred.
1642 */
1643enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1644{
1645 struct htx *htx = htxbuf(&check->bi);
1646 struct htx_sl *sl;
1647 struct htx_blk *blk;
1648 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1649 struct tcpcheck_expect *expect = &rule->expect;
1650 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1651 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1652 struct ist desc = IST_NULL;
1653 int i, match, inverse;
1654
Christopher Faulet147b8c92021-04-10 09:00:38 +02001655 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1656
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001657 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001658
1659 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001660 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001661 status = HCHK_STATUS_L7RSP;
1662 goto error;
1663 }
1664
1665 if (htx_is_empty(htx)) {
1666 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001667 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001668 status = HCHK_STATUS_L7RSP;
1669 goto error;
1670 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001671 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001672 goto wait_more_data;
1673 }
1674
1675 sl = http_get_stline(htx);
1676 check->code = sl->info.res.status;
1677
1678 if (check->server &&
1679 (check->server->proxy->options & PR_O_DISABLE404) &&
1680 (check->server->next_state != SRV_ST_STOPPED) &&
1681 (check->code == 404)) {
1682 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001683 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001684 goto out;
1685 }
1686
1687 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1688 /* Make GCC happy ; initialize match to a failure state. */
1689 match = inverse;
1690 status = expect->err_status;
1691
1692 switch (expect->type) {
1693 case TCPCHK_EXPECT_HTTP_STATUS:
1694 match = 0;
1695 for (i = 0; i < expect->codes.num; i++) {
1696 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1697 sl->info.res.status <= expect->codes.codes[i][1]) {
1698 match = 1;
1699 break;
1700 }
1701 }
1702
1703 /* Set status and description in case of error */
1704 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1705 if (LIST_ISEMPTY(&expect->onerror_fmt))
1706 desc = htx_sl_res_reason(sl);
1707 break;
1708 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1709 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1710
1711 /* Set status and description in case of error */
1712 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1713 if (LIST_ISEMPTY(&expect->onerror_fmt))
1714 desc = htx_sl_res_reason(sl);
1715 break;
1716
1717 case TCPCHK_EXPECT_HTTP_HEADER: {
1718 struct http_hdr_ctx ctx;
1719 struct ist npat, vpat, value;
1720 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1721
1722 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1723 nbuf = alloc_trash_chunk();
1724 if (!nbuf) {
1725 status = HCHK_STATUS_L7RSP;
1726 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001727 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001728 goto error;
1729 }
1730 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1731 if (!b_data(nbuf)) {
1732 status = HCHK_STATUS_L7RSP;
1733 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001734 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001735 goto error;
1736 }
1737 npat = ist2(b_orig(nbuf), b_data(nbuf));
1738 }
1739 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1740 npat = expect->hdr.name;
1741
1742 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1743 vbuf = alloc_trash_chunk();
1744 if (!vbuf) {
1745 status = HCHK_STATUS_L7RSP;
1746 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001747 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001748 goto error;
1749 }
1750 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1751 if (!b_data(vbuf)) {
1752 status = HCHK_STATUS_L7RSP;
1753 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001754 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001755 goto error;
1756 }
1757 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1758 }
1759 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1760 vpat = expect->hdr.value;
1761
1762 match = 0;
1763 ctx.blk = NULL;
1764 while (1) {
1765 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1766 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1767 if (!http_find_str_header(htx, npat, &ctx, full))
1768 goto end_of_match;
1769 break;
1770 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1771 if (!http_find_pfx_header(htx, npat, &ctx, full))
1772 goto end_of_match;
1773 break;
1774 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1775 if (!http_find_sfx_header(htx, npat, &ctx, full))
1776 goto end_of_match;
1777 break;
1778 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1779 if (!http_find_sub_header(htx, npat, &ctx, full))
1780 goto end_of_match;
1781 break;
1782 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1783 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1784 goto end_of_match;
1785 break;
1786 default:
1787 /* should never happen */
1788 goto end_of_match;
1789 }
1790
1791 /* A header has matched the name pattern, let's test its
1792 * value now (always defined from there). If there is no
1793 * value pattern, it is a good match.
1794 */
1795
1796 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1797 match = 1;
1798 goto end_of_match;
1799 }
1800
1801 value = ctx.value;
1802 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1803 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1804 if (isteq(value, vpat)) {
1805 match = 1;
1806 goto end_of_match;
1807 }
1808 break;
1809 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1810 if (istlen(value) < istlen(vpat))
1811 break;
1812 value = ist2(istptr(value), istlen(vpat));
1813 if (isteq(value, vpat)) {
1814 match = 1;
1815 goto end_of_match;
1816 }
1817 break;
1818 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1819 if (istlen(value) < istlen(vpat))
1820 break;
1821 value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
1822 if (isteq(value, vpat)) {
1823 match = 1;
1824 goto end_of_match;
1825 }
1826 break;
1827 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1828 if (isttest(istist(value, vpat))) {
1829 match = 1;
1830 goto end_of_match;
1831 }
1832 break;
1833 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1834 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1835 match = 1;
1836 goto end_of_match;
1837 }
1838 break;
1839 }
1840 }
1841
1842 end_of_match:
1843 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1844 if (LIST_ISEMPTY(&expect->onerror_fmt))
1845 desc = htx_sl_res_reason(sl);
1846 break;
1847 }
1848
1849 case TCPCHK_EXPECT_HTTP_BODY:
1850 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1851 case TCPCHK_EXPECT_HTTP_BODY_LF:
1852 match = 0;
1853 chunk_reset(&trash);
1854 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1855 enum htx_blk_type type = htx_get_blk_type(blk);
1856
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001857 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001858 break;
1859 if (type == HTX_BLK_DATA) {
1860 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1861 break;
1862 }
1863 }
1864
1865 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001866 if (!last_read) {
1867 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001868 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001869 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001870 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1871 if (LIST_ISEMPTY(&expect->onerror_fmt))
1872 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001873 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001874 goto error;
1875 }
1876
1877 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1878 tmp = alloc_trash_chunk();
1879 if (!tmp) {
1880 status = HCHK_STATUS_L7RSP;
1881 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001882 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001883 goto error;
1884 }
1885 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1886 if (!b_data(tmp)) {
1887 status = HCHK_STATUS_L7RSP;
1888 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001889 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001890 goto error;
1891 }
1892 }
1893
1894 if (!last_read &&
1895 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1896 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1897 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1898 ret = TCPCHK_EVAL_WAIT;
1899 goto out;
1900 }
1901
1902 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1903 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1904 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1905 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1906 else
1907 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1908
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001909 /* Wait for more data on mismatch only if no minimum is defined (-1),
1910 * otherwise the absence of match is already conclusive.
1911 */
1912 if (!match && !last_read && (expect->min_recv == -1)) {
1913 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001914 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001915 goto out;
1916 }
1917
Willy Tarreau51cd5952020-06-05 12:25:38 +02001918 /* Set status and description in case of error */
1919 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1920 if (LIST_ISEMPTY(&expect->onerror_fmt))
1921 desc = (inverse
1922 ? ist("HTTP check matched unwanted content")
1923 : ist("HTTP content check did not match"));
1924 break;
1925
1926
1927 default:
1928 /* should never happen */
1929 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1930 goto error;
1931 }
1932
Christopher Faulet147b8c92021-04-10 09:00:38 +02001933 if (!(match ^ inverse)) {
1934 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001935 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001936 }
1937
1938 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001939
1940 out:
1941 free_trash_chunk(tmp);
1942 free_trash_chunk(nbuf);
1943 free_trash_chunk(vbuf);
1944 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001945 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001946 return ret;
1947
1948 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001949 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001950 ret = TCPCHK_EVAL_STOP;
1951 msg = alloc_trash_chunk();
1952 if (msg)
1953 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1954 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1955 goto out;
1956
1957 wait_more_data:
1958 ret = TCPCHK_EVAL_WAIT;
1959 goto out;
1960}
1961
1962/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1963 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1964 * if an error occurred.
1965 */
1966enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1967{
1968 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1969 struct tcpcheck_expect *expect = &rule->expect;
1970 struct buffer *msg = NULL, *tmp = NULL;
1971 struct ist desc = IST_NULL;
1972 enum healthcheck_status status;
1973 int match, inverse;
1974
Christopher Faulet147b8c92021-04-10 09:00:38 +02001975 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1976
Willy Tarreau51cd5952020-06-05 12:25:38 +02001977 last_read |= b_full(&check->bi);
1978
1979 /* The current expect might need more data than the previous one, check again
1980 * that the minimum amount data required to match is respected.
1981 */
1982 if (!last_read) {
1983 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1984 (b_data(&check->bi) < istlen(expect->data))) {
1985 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001986 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001987 goto out;
1988 }
1989 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1990 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001991 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001992 goto out;
1993 }
1994 }
1995
1996 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1997 /* Make GCC happy ; initialize match to a failure state. */
1998 match = inverse;
1999 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
2000
2001 switch (expect->type) {
2002 case TCPCHK_EXPECT_STRING:
2003 case TCPCHK_EXPECT_BINARY:
2004 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2005 break;
2006 case TCPCHK_EXPECT_STRING_REGEX:
2007 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2008 break;
2009
2010 case TCPCHK_EXPECT_BINARY_REGEX:
2011 chunk_reset(&trash);
2012 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2013 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2014 break;
2015
2016 case TCPCHK_EXPECT_STRING_LF:
2017 case TCPCHK_EXPECT_BINARY_LF:
2018 match = 0;
2019 tmp = alloc_trash_chunk();
2020 if (!tmp) {
2021 status = HCHK_STATUS_L7RSP;
2022 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002023 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002024 goto error;
2025 }
2026 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2027 if (!b_data(tmp)) {
2028 status = HCHK_STATUS_L7RSP;
2029 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002030 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002031 goto error;
2032 }
2033 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2034 int len = tmp->data;
2035 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2036 status = HCHK_STATUS_L7RSP;
2037 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002038 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002039 goto error;
2040 }
2041 tmp->data = len;
2042 }
2043 if (b_data(&check->bi) < tmp->data) {
2044 if (!last_read) {
2045 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002046 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002047 goto out;
2048 }
2049 break;
2050 }
2051 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2052 break;
2053
2054 case TCPCHK_EXPECT_CUSTOM:
2055 if (expect->custom)
2056 ret = expect->custom(check, rule, last_read);
2057 goto out;
2058 default:
2059 /* Should never happen. */
2060 ret = TCPCHK_EVAL_STOP;
2061 goto out;
2062 }
2063
2064
2065 /* Wait for more data on mismatch only if no minimum is defined (-1),
2066 * otherwise the absence of match is already conclusive.
2067 */
2068 if (!match && !last_read && (expect->min_recv == -1)) {
2069 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002070 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002071 goto out;
2072 }
2073
2074 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002075 if (match ^ inverse) {
2076 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002077 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002078 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002079
2080 error:
2081 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002082 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002083 ret = TCPCHK_EVAL_STOP;
2084 msg = alloc_trash_chunk();
2085 if (msg)
2086 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2087 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2088 free_trash_chunk(msg);
2089
2090 out:
2091 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002092 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002093 return ret;
2094}
2095
2096/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2097 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2098 * waits.
2099 */
2100enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2101{
2102 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2103 struct act_rule *act_rule;
2104 enum act_return act_ret;
2105
2106 act_rule =rule->action_kw.rule;
2107 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2108 if (act_ret != ACT_RET_CONT) {
2109 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2110 tcpcheck_get_step_id(check, rule));
2111 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2112 ret = TCPCHK_EVAL_STOP;
2113 }
2114
2115 return ret;
2116}
2117
2118/* Executes a tcp-check ruleset. Note that this is called both from the
2119 * connection's wake() callback and from the check scheduling task. It returns
2120 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2121 * presenting the risk of an fd replacement.
2122 *
2123 * Please do NOT place any return statement in this function and only leave
2124 * via the out_end_tcpcheck label after setting retcode.
2125 */
2126int tcpcheck_main(struct check *check)
2127{
2128 struct tcpcheck_rule *rule;
2129 struct conn_stream *cs = check->cs;
2130 struct connection *conn = cs_conn(cs);
2131 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002132 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002133 enum tcpcheck_eval_ret eval_ret;
2134
2135 /* here, we know that the check is complete or that it failed */
2136 if (check->result != CHK_RES_UNKNOWN)
2137 goto out;
2138
Christopher Faulet147b8c92021-04-10 09:00:38 +02002139 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2140
Willy Tarreau51cd5952020-06-05 12:25:38 +02002141 /* Note: the conn-stream and the connection may only be undefined before
2142 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002143 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002144 */
2145
2146 /* 1- check for connection error, if any */
2147 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2148 goto out_end_tcpcheck;
2149
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002150 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002151 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002152 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002153 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002154 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2155 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002156
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002157 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002158 * tcp-check variables */
2159 else {
2160 struct tcpcheck_var *var;
2161
2162 /* First evaluation, create a session */
2163 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2164 if (!check->sess) {
2165 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002166 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002167 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2168 goto out_end_tcpcheck;
2169 }
2170 vars_init(&check->vars, SCOPE_CHECK);
2171 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2172
2173 /* Preset tcp-check variables */
2174 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2175 struct sample smp;
2176
2177 memset(&smp, 0, sizeof(smp));
2178 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2179 smp.data = var->data;
2180 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2181 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002182 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002183 }
2184
2185 /* Now evaluate the tcp-check rules */
2186
2187 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2188 check->code = 0;
2189 switch (rule->action) {
2190 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002191 /* Not the first connection, release it first */
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002192 if (cs && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002193 check->state |= CHK_ST_CLOSE_CONN;
2194 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002195 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002196
2197 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002198
2199 /* We are still waiting the connection gets closed */
2200 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002201 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002202 eval_ret = TCPCHK_EVAL_WAIT;
2203 break;
2204 }
2205
Christopher Faulet147b8c92021-04-10 09:00:38 +02002206 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002207 eval_ret = tcpcheck_eval_connect(check, rule);
2208
2209 /* Refresh conn-stream and connection */
2210 cs = check->cs;
2211 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002212 last_read = 0;
2213 must_read = ((cs && IS_HTX_CS(cs)) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002214 break;
2215 case TCPCHK_ACT_SEND:
2216 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002217 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002218 eval_ret = tcpcheck_eval_send(check, rule);
2219 must_read = 1;
2220 break;
2221 case TCPCHK_ACT_EXPECT:
2222 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002223 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002224 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002225 eval_ret = tcpcheck_eval_recv(check, rule);
2226 if (eval_ret == TCPCHK_EVAL_STOP)
2227 goto out_end_tcpcheck;
2228 else if (eval_ret == TCPCHK_EVAL_WAIT)
2229 goto out;
2230 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2231 must_read = 0;
2232 }
2233
2234 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2235 ? tcpcheck_eval_expect_http(check, rule, last_read)
2236 : tcpcheck_eval_expect(check, rule, last_read));
2237
2238 if (eval_ret == TCPCHK_EVAL_WAIT) {
2239 check->current_step = rule->expect.head;
2240 if (!(check->wait_list.events & SUB_RETRY_RECV))
2241 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2242 }
2243 break;
2244 case TCPCHK_ACT_ACTION_KW:
2245 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002246 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002247 eval_ret = tcpcheck_eval_action_kw(check, rule);
2248 break;
2249 default:
2250 /* Otherwise, just go to the next one and don't update
2251 * the current step
2252 */
2253 eval_ret = TCPCHK_EVAL_CONTINUE;
2254 break;
2255 }
2256
2257 switch (eval_ret) {
2258 case TCPCHK_EVAL_CONTINUE:
2259 break;
2260 case TCPCHK_EVAL_WAIT:
2261 goto out;
2262 case TCPCHK_EVAL_STOP:
2263 goto out_end_tcpcheck;
2264 }
2265 }
2266
2267 /* All rules was evaluated */
2268 if (check->current_step) {
2269 rule = check->current_step;
2270
Christopher Faulet147b8c92021-04-10 09:00:38 +02002271 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2272
Willy Tarreau51cd5952020-06-05 12:25:38 +02002273 if (rule->action == TCPCHK_ACT_EXPECT) {
2274 struct buffer *msg;
2275 enum healthcheck_status status;
2276
2277 if (check->server &&
2278 (check->server->proxy->options & PR_O_DISABLE404) &&
2279 (check->server->next_state != SRV_ST_STOPPED) &&
2280 (check->code == 404)) {
2281 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002282 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002283 goto out_end_tcpcheck;
2284 }
2285
2286 msg = alloc_trash_chunk();
2287 if (msg)
2288 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2289 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2290 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2291 free_trash_chunk(msg);
2292 }
2293 else if (rule->action == TCPCHK_ACT_CONNECT) {
2294 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2295 enum healthcheck_status status = HCHK_STATUS_L4OK;
2296#ifdef USE_OPENSSL
2297 if (ssl_sock_is_ssl(conn))
2298 status = HCHK_STATUS_L6OK;
2299#endif
2300 set_server_check_status(check, status, msg);
2301 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002302 else
2303 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002304 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002305 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002306 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002307 }
2308 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002309
2310 out_end_tcpcheck:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002311 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) {
2312 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002313 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002314 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002315
Christopher Fauletb381a502020-11-25 13:47:00 +01002316 /* the tcpcheck is finished, release in/out buffer now */
2317 check_release_buf(check, &check->bi);
2318 check_release_buf(check, &check->bo);
2319
Willy Tarreau51cd5952020-06-05 12:25:38 +02002320 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002321 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002322 return retcode;
2323}
2324
2325
2326/**************************************************************************/
2327/******************* Internals to parse tcp-check rules *******************/
2328/**************************************************************************/
2329struct action_kw_list tcp_check_keywords = {
2330 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2331};
2332
2333/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2334 * returned on error.
2335 */
2336struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2337 struct list *rules, struct action_kw *kw,
2338 const char *file, int line, char **errmsg)
2339{
2340 struct tcpcheck_rule *chk = NULL;
2341 struct act_rule *actrule = NULL;
2342
2343 actrule = calloc(1, sizeof(*actrule));
2344 if (!actrule) {
2345 memprintf(errmsg, "out of memory");
2346 goto error;
2347 }
2348 actrule->kw = kw;
2349 actrule->from = ACT_F_TCP_CHK;
2350
2351 cur_arg++;
2352 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2353 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2354 goto error;
2355 }
2356
2357 chk = calloc(1, sizeof(*chk));
2358 if (!chk) {
2359 memprintf(errmsg, "out of memory");
2360 goto error;
2361 }
2362 chk->action = TCPCHK_ACT_ACTION_KW;
2363 chk->action_kw.rule = actrule;
2364 return chk;
2365
2366 error:
2367 free(actrule);
2368 return NULL;
2369}
2370
2371/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2372 * returned on error.
2373 */
2374struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2375 const char *file, int line, char **errmsg)
2376{
2377 struct tcpcheck_rule *chk = NULL;
2378 struct sockaddr_storage *sk = NULL;
2379 char *comment = NULL, *sni = NULL, *alpn = NULL;
2380 struct sample_expr *port_expr = NULL;
2381 const struct mux_proto_list *mux_proto = NULL;
2382 unsigned short conn_opts = 0;
2383 long port = 0;
2384 int alpn_len = 0;
2385
2386 list_for_each_entry(chk, rules, list) {
2387 if (chk->action == TCPCHK_ACT_CONNECT)
2388 break;
2389 if (chk->action == TCPCHK_ACT_COMMENT ||
2390 chk->action == TCPCHK_ACT_ACTION_KW ||
2391 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2392 continue;
2393
2394 memprintf(errmsg, "first step MUST also be a 'connect', "
2395 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2396 "when there is a 'connect' step in the tcp-check ruleset");
2397 goto error;
2398 }
2399
2400 cur_arg++;
2401 while (*(args[cur_arg])) {
2402 if (strcmp(args[cur_arg], "default") == 0)
2403 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2404 else if (strcmp(args[cur_arg], "addr") == 0) {
2405 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002406
2407 if (!*(args[cur_arg+1])) {
2408 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2409 goto error;
2410 }
2411
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002412 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2413 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002414 if (!sk) {
2415 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2416 goto error;
2417 }
2418
Willy Tarreau51cd5952020-06-05 12:25:38 +02002419 cur_arg++;
2420 }
2421 else if (strcmp(args[cur_arg], "port") == 0) {
2422 const char *p, *end;
2423
2424 if (!*(args[cur_arg+1])) {
2425 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2426 goto error;
2427 }
2428 cur_arg++;
2429
2430 port = 0;
2431 release_sample_expr(port_expr);
2432 p = args[cur_arg]; end = p + strlen(p);
2433 port = read_uint(&p, end);
2434 if (p != end) {
2435 int idx = 0;
2436
2437 px->conf.args.ctx = ARGC_SRV;
2438 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet8551c342021-09-30 16:22:51 +02002439 file, line, errmsg, (px->cap & PR_CAP_DEF) ? NULL: &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002440
2441 if (!port_expr) {
2442 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2443 goto error;
2444 }
2445 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2446 memprintf(errmsg, "error detected while parsing port expression : "
2447 " fetch method '%s' extracts information from '%s', "
2448 "none of which is available here.\n",
2449 args[cur_arg], sample_src_names(port_expr->fetch->use));
2450 goto error;
2451 }
2452 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2453 }
2454 else if (port > 65535 || port < 1) {
2455 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2456 args[cur_arg]);
2457 goto error;
2458 }
2459 }
2460 else if (strcmp(args[cur_arg], "proto") == 0) {
2461 if (!*(args[cur_arg+1])) {
2462 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2463 goto error;
2464 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002465 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002466 if (!mux_proto) {
2467 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2468 goto error;
2469 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002470
2471 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2472 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2473 goto error;
2474 }
2475 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2476 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2477 goto error;
2478 }
2479
Willy Tarreau51cd5952020-06-05 12:25:38 +02002480 cur_arg++;
2481 }
2482 else if (strcmp(args[cur_arg], "comment") == 0) {
2483 if (!*(args[cur_arg+1])) {
2484 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2485 goto error;
2486 }
2487 cur_arg++;
2488 free(comment);
2489 comment = strdup(args[cur_arg]);
2490 if (!comment) {
2491 memprintf(errmsg, "out of memory");
2492 goto error;
2493 }
2494 }
2495 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2496 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2497 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2498 conn_opts |= TCPCHK_OPT_SOCKS4;
2499 else if (strcmp(args[cur_arg], "linger") == 0)
2500 conn_opts |= TCPCHK_OPT_LINGER;
2501#ifdef USE_OPENSSL
2502 else if (strcmp(args[cur_arg], "ssl") == 0) {
2503 px->options |= PR_O_TCPCHK_SSL;
2504 conn_opts |= TCPCHK_OPT_SSL;
2505 }
2506 else if (strcmp(args[cur_arg], "sni") == 0) {
2507 if (!*(args[cur_arg+1])) {
2508 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2509 goto error;
2510 }
2511 cur_arg++;
2512 free(sni);
2513 sni = strdup(args[cur_arg]);
2514 if (!sni) {
2515 memprintf(errmsg, "out of memory");
2516 goto error;
2517 }
2518 }
2519 else if (strcmp(args[cur_arg], "alpn") == 0) {
2520#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2521 free(alpn);
2522 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2523 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2524 goto error;
2525 }
2526 cur_arg++;
2527#else
2528 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2529 goto error;
2530#endif
2531 }
2532#endif /* USE_OPENSSL */
2533
2534 else {
2535 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2536#ifdef USE_OPENSSL
2537 ", 'ssl', 'sni', 'alpn'"
2538#endif /* USE_OPENSSL */
2539 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2540 args[cur_arg]);
2541 goto error;
2542 }
2543 cur_arg++;
2544 }
2545
2546 chk = calloc(1, sizeof(*chk));
2547 if (!chk) {
2548 memprintf(errmsg, "out of memory");
2549 goto error;
2550 }
2551 chk->action = TCPCHK_ACT_CONNECT;
2552 chk->comment = comment;
2553 chk->connect.port = port;
2554 chk->connect.options = conn_opts;
2555 chk->connect.sni = sni;
2556 chk->connect.alpn = alpn;
2557 chk->connect.alpn_len= alpn_len;
2558 chk->connect.port_expr= port_expr;
2559 chk->connect.mux_proto= mux_proto;
2560 if (sk)
2561 chk->connect.addr = *sk;
2562 return chk;
2563
2564 error:
2565 free(alpn);
2566 free(sni);
2567 free(comment);
2568 release_sample_expr(port_expr);
2569 return NULL;
2570}
2571
2572/* Parses and creates a tcp-check send rule. NULL is returned on error */
2573struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2574 const char *file, int line, char **errmsg)
2575{
2576 struct tcpcheck_rule *chk = NULL;
2577 char *comment = NULL, *data = NULL;
2578 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2579
2580 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2581 type = TCPCHK_SEND_BINARY_LF;
2582 else if (strcmp(args[cur_arg], "send-binary") == 0)
2583 type = TCPCHK_SEND_BINARY;
2584 else if (strcmp(args[cur_arg], "send-lf") == 0)
2585 type = TCPCHK_SEND_STRING_LF;
2586 else if (strcmp(args[cur_arg], "send") == 0)
2587 type = TCPCHK_SEND_STRING;
2588
2589 if (!*(args[cur_arg+1])) {
2590 memprintf(errmsg, "'%s' expects a %s as argument",
2591 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2592 goto error;
2593 }
2594
2595 data = args[cur_arg+1];
2596
2597 cur_arg += 2;
2598 while (*(args[cur_arg])) {
2599 if (strcmp(args[cur_arg], "comment") == 0) {
2600 if (!*(args[cur_arg+1])) {
2601 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2602 goto error;
2603 }
2604 cur_arg++;
2605 free(comment);
2606 comment = strdup(args[cur_arg]);
2607 if (!comment) {
2608 memprintf(errmsg, "out of memory");
2609 goto error;
2610 }
2611 }
2612 else {
2613 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2614 args[cur_arg]);
2615 goto error;
2616 }
2617 cur_arg++;
2618 }
2619
2620 chk = calloc(1, sizeof(*chk));
2621 if (!chk) {
2622 memprintf(errmsg, "out of memory");
2623 goto error;
2624 }
2625 chk->action = TCPCHK_ACT_SEND;
2626 chk->comment = comment;
2627 chk->send.type = type;
2628
2629 switch (chk->send.type) {
2630 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002631 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002632 if (!isttest(chk->send.data)) {
2633 memprintf(errmsg, "out of memory");
2634 goto error;
2635 }
2636 break;
2637 case TCPCHK_SEND_BINARY: {
2638 int len = chk->send.data.len;
2639 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2640 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2641 goto error;
2642 }
2643 chk->send.data.len = len;
2644 break;
2645 }
2646 case TCPCHK_SEND_STRING_LF:
2647 case TCPCHK_SEND_BINARY_LF:
2648 LIST_INIT(&chk->send.fmt);
2649 px->conf.args.ctx = ARGC_SRV;
2650 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2651 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2652 goto error;
2653 }
2654 break;
2655 case TCPCHK_SEND_HTTP:
2656 case TCPCHK_SEND_UNDEF:
2657 goto error;
2658 }
2659
2660 return chk;
2661
2662 error:
2663 free(chk);
2664 free(comment);
2665 return NULL;
2666}
2667
2668/* Parses and creates a http-check send rule. NULL is returned on error */
2669struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2670 const char *file, int line, char **errmsg)
2671{
2672 struct tcpcheck_rule *chk = NULL;
2673 struct tcpcheck_http_hdr *hdr = NULL;
2674 struct http_hdr hdrs[global.tune.max_http_hdr];
2675 char *meth = NULL, *uri = NULL, *vsn = NULL;
2676 char *body = NULL, *comment = NULL;
2677 unsigned int flags = 0;
2678 int i = 0, host_hdr = -1;
2679
2680 cur_arg++;
2681 while (*(args[cur_arg])) {
2682 if (strcmp(args[cur_arg], "meth") == 0) {
2683 if (!*(args[cur_arg+1])) {
2684 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2685 goto error;
2686 }
2687 cur_arg++;
2688 meth = args[cur_arg];
2689 }
2690 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2691 if (!*(args[cur_arg+1])) {
2692 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2693 goto error;
2694 }
2695 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2696 if (strcmp(args[cur_arg], "uri-lf") == 0)
2697 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2698 cur_arg++;
2699 uri = args[cur_arg];
2700 }
2701 else if (strcmp(args[cur_arg], "ver") == 0) {
2702 if (!*(args[cur_arg+1])) {
2703 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2704 goto error;
2705 }
2706 cur_arg++;
2707 vsn = args[cur_arg];
2708 }
2709 else if (strcmp(args[cur_arg], "hdr") == 0) {
2710 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2711 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2712 goto error;
2713 }
2714
2715 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2716 if (host_hdr >= 0) {
2717 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2718 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2719 goto error;
2720 }
2721 host_hdr = i;
2722 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002723 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002724 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2725 goto skip_hdr;
2726
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002727 hdrs[i].n = ist(args[cur_arg + 1]);
2728 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002729 i++;
2730 skip_hdr:
2731 cur_arg += 2;
2732 }
2733 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2734 if (!*(args[cur_arg+1])) {
2735 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2736 goto error;
2737 }
2738 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2739 if (strcmp(args[cur_arg], "body-lf") == 0)
2740 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2741 cur_arg++;
2742 body = args[cur_arg];
2743 }
2744 else if (strcmp(args[cur_arg], "comment") == 0) {
2745 if (!*(args[cur_arg+1])) {
2746 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2747 goto error;
2748 }
2749 cur_arg++;
2750 free(comment);
2751 comment = strdup(args[cur_arg]);
2752 if (!comment) {
2753 memprintf(errmsg, "out of memory");
2754 goto error;
2755 }
2756 }
2757 else {
2758 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2759 " but got '%s' as argument.", args[cur_arg]);
2760 goto error;
2761 }
2762 cur_arg++;
2763 }
2764
2765 hdrs[i].n = hdrs[i].v = IST_NULL;
2766
2767 chk = calloc(1, sizeof(*chk));
2768 if (!chk) {
2769 memprintf(errmsg, "out of memory");
2770 goto error;
2771 }
2772 chk->action = TCPCHK_ACT_SEND;
2773 chk->comment = comment; comment = NULL;
2774 chk->send.type = TCPCHK_SEND_HTTP;
2775 chk->send.http.flags = flags;
2776 LIST_INIT(&chk->send.http.hdrs);
2777
2778 if (meth) {
2779 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2780 chk->send.http.meth.str.area = strdup(meth);
2781 chk->send.http.meth.str.data = strlen(meth);
2782 if (!chk->send.http.meth.str.area) {
2783 memprintf(errmsg, "out of memory");
2784 goto error;
2785 }
2786 }
2787 if (uri) {
2788 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2789 LIST_INIT(&chk->send.http.uri_fmt);
2790 px->conf.args.ctx = ARGC_SRV;
2791 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2792 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2793 goto error;
2794 }
2795 }
2796 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002797 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002798 if (!isttest(chk->send.http.uri)) {
2799 memprintf(errmsg, "out of memory");
2800 goto error;
2801 }
2802 }
2803 }
2804 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002805 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002806 if (!isttest(chk->send.http.vsn)) {
2807 memprintf(errmsg, "out of memory");
2808 goto error;
2809 }
2810 }
2811 for (i = 0; istlen(hdrs[i].n); i++) {
2812 hdr = calloc(1, sizeof(*hdr));
2813 if (!hdr) {
2814 memprintf(errmsg, "out of memory");
2815 goto error;
2816 }
2817 LIST_INIT(&hdr->value);
2818 hdr->name = istdup(hdrs[i].n);
2819 if (!isttest(hdr->name)) {
2820 memprintf(errmsg, "out of memory");
2821 goto error;
2822 }
2823
2824 ist0(hdrs[i].v);
2825 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2826 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002827 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002828 hdr = NULL;
2829 }
2830
2831 if (body) {
2832 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2833 LIST_INIT(&chk->send.http.body_fmt);
2834 px->conf.args.ctx = ARGC_SRV;
2835 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2836 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2837 goto error;
2838 }
2839 }
2840 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002841 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002842 if (!isttest(chk->send.http.body)) {
2843 memprintf(errmsg, "out of memory");
2844 goto error;
2845 }
2846 }
2847 }
2848
2849 return chk;
2850
2851 error:
2852 free_tcpcheck_http_hdr(hdr);
2853 free_tcpcheck(chk, 0);
2854 free(comment);
2855 return NULL;
2856}
2857
2858/* Parses and creates a http-check comment rule. NULL is returned on error */
2859struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2860 const char *file, int line, char **errmsg)
2861{
2862 struct tcpcheck_rule *chk = NULL;
2863 char *comment = NULL;
2864
2865 if (!*(args[cur_arg+1])) {
2866 memprintf(errmsg, "expects a string as argument");
2867 goto error;
2868 }
2869 cur_arg++;
2870 comment = strdup(args[cur_arg]);
2871 if (!comment) {
2872 memprintf(errmsg, "out of memory");
2873 goto error;
2874 }
2875
2876 chk = calloc(1, sizeof(*chk));
2877 if (!chk) {
2878 memprintf(errmsg, "out of memory");
2879 goto error;
2880 }
2881 chk->action = TCPCHK_ACT_COMMENT;
2882 chk->comment = comment;
2883 return chk;
2884
2885 error:
2886 free(comment);
2887 return NULL;
2888}
2889
2890/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2891 * on error. <proto> is set to the right protocol flags (covered by the
2892 * TCPCHK_RULES_PROTO_CHK mask).
2893 */
2894struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2895 struct list *rules, unsigned int proto,
2896 const char *file, int line, char **errmsg)
2897{
2898 struct tcpcheck_rule *prev_check, *chk = NULL;
2899 struct sample_expr *status_expr = NULL;
2900 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2901 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2902 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2903 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2904 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2905 unsigned int flags = 0;
2906 long min_recv = -1;
2907 int inverse = 0;
2908
2909 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2910 if (!*(args[cur_arg+1])) {
2911 memprintf(errmsg, "expects at least a matching pattern as arguments");
2912 goto error;
2913 }
2914
2915 cur_arg++;
2916 while (*(args[cur_arg])) {
2917 int in_pattern = 0;
2918
2919 rescan:
2920 if (strcmp(args[cur_arg], "min-recv") == 0) {
2921 if (in_pattern) {
2922 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2923 goto error;
2924 }
2925 if (!*(args[cur_arg+1])) {
2926 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2927 goto error;
2928 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002929 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002930 cur_arg++;
2931 min_recv = atol(args[cur_arg]);
2932 if (min_recv < -1 || min_recv > INT_MAX) {
2933 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2934 goto error;
2935 }
2936 }
2937 else if (*(args[cur_arg]) == '!') {
2938 in_pattern = 1;
2939 while (*(args[cur_arg]) == '!') {
2940 inverse = !inverse;
2941 args[cur_arg]++;
2942 }
2943 if (!*(args[cur_arg]))
2944 cur_arg++;
2945 goto rescan;
2946 }
2947 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2948 if (type != TCPCHK_EXPECT_UNDEF) {
2949 memprintf(errmsg, "only on pattern expected");
2950 goto error;
2951 }
2952 if (proto != TCPCHK_RULES_HTTP_CHK)
2953 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2954 else
2955 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2956
2957 if (!*(args[cur_arg+1])) {
2958 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2959 goto error;
2960 }
2961 cur_arg++;
2962 pattern = args[cur_arg];
2963 }
2964 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2965 if (proto == TCPCHK_RULES_HTTP_CHK)
2966 goto bad_http_kw;
2967 if (type != TCPCHK_EXPECT_UNDEF) {
2968 memprintf(errmsg, "only on pattern expected");
2969 goto error;
2970 }
2971 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2972
2973 if (!*(args[cur_arg+1])) {
2974 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2975 goto error;
2976 }
2977 cur_arg++;
2978 pattern = args[cur_arg];
2979 }
2980 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2981 if (type != TCPCHK_EXPECT_UNDEF) {
2982 memprintf(errmsg, "only on pattern expected");
2983 goto error;
2984 }
2985 if (proto != TCPCHK_RULES_HTTP_CHK)
2986 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2987 else {
2988 if (*(args[cur_arg]) != 's')
2989 goto bad_http_kw;
2990 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2991 }
2992
2993 if (!*(args[cur_arg+1])) {
2994 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2995 goto error;
2996 }
2997 cur_arg++;
2998 pattern = args[cur_arg];
2999 }
3000 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3001 if (proto != TCPCHK_RULES_HTTP_CHK)
3002 goto bad_tcp_kw;
3003 if (type != TCPCHK_EXPECT_UNDEF) {
3004 memprintf(errmsg, "only on pattern expected");
3005 goto error;
3006 }
3007 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3008
3009 if (!*(args[cur_arg+1])) {
3010 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3011 goto error;
3012 }
3013 cur_arg++;
3014 pattern = args[cur_arg];
3015 }
3016 else if (strcmp(args[cur_arg], "custom") == 0) {
3017 if (in_pattern) {
3018 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3019 goto error;
3020 }
3021 if (type != TCPCHK_EXPECT_UNDEF) {
3022 memprintf(errmsg, "only on pattern expected");
3023 goto error;
3024 }
3025 type = TCPCHK_EXPECT_CUSTOM;
3026 }
3027 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3028 int orig_arg = cur_arg;
3029
3030 if (proto != TCPCHK_RULES_HTTP_CHK)
3031 goto bad_tcp_kw;
3032 if (type != TCPCHK_EXPECT_UNDEF) {
3033 memprintf(errmsg, "only on pattern expected");
3034 goto error;
3035 }
3036 type = TCPCHK_EXPECT_HTTP_HEADER;
3037
3038 if (strcmp(args[cur_arg], "fhdr") == 0)
3039 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3040
3041 /* Parse the name pattern, mandatory */
3042 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3043 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3044 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3045 args[orig_arg]);
3046 goto error;
3047 }
3048
3049 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3050 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3051
3052 cur_arg += 2;
3053 if (strcmp(args[cur_arg], "-m") == 0) {
3054 if (!*(args[cur_arg+1])) {
3055 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3056 args[orig_arg], args[cur_arg]);
3057 goto error;
3058 }
3059 if (strcmp(args[cur_arg+1], "str") == 0)
3060 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3061 else if (strcmp(args[cur_arg+1], "beg") == 0)
3062 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3063 else if (strcmp(args[cur_arg+1], "end") == 0)
3064 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3065 else if (strcmp(args[cur_arg+1], "sub") == 0)
3066 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3067 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3068 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3069 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3070 args[orig_arg]);
3071 goto error;
3072 }
3073 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3074 }
3075 else {
3076 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3077 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3078 goto error;
3079 }
3080 cur_arg += 2;
3081 }
3082 else
3083 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3084 npat = args[cur_arg];
3085
3086 if (!*(args[cur_arg+1]) ||
3087 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3088 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3089 goto next;
3090 }
3091 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3092 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3093
3094 /* Parse the value pattern, optional */
3095 if (strcmp(args[cur_arg+2], "-m") == 0) {
3096 cur_arg += 2;
3097 if (!*(args[cur_arg+1])) {
3098 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3099 args[orig_arg], args[cur_arg]);
3100 goto error;
3101 }
3102 if (strcmp(args[cur_arg+1], "str") == 0)
3103 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3104 else if (strcmp(args[cur_arg+1], "beg") == 0)
3105 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3106 else if (strcmp(args[cur_arg+1], "end") == 0)
3107 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3108 else if (strcmp(args[cur_arg+1], "sub") == 0)
3109 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3110 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3111 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3112 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3113 args[orig_arg]);
3114 goto error;
3115 }
3116 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3117 }
3118 else {
3119 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3120 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3121 goto error;
3122 }
3123 }
3124 else
3125 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3126
3127 if (!*(args[cur_arg+2])) {
3128 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3129 goto error;
3130 }
3131 vpat = args[cur_arg+2];
3132 cur_arg += 2;
3133 }
3134 else if (strcmp(args[cur_arg], "comment") == 0) {
3135 if (in_pattern) {
3136 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3137 goto error;
3138 }
3139 if (!*(args[cur_arg+1])) {
3140 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3141 goto error;
3142 }
3143 cur_arg++;
3144 free(comment);
3145 comment = strdup(args[cur_arg]);
3146 if (!comment) {
3147 memprintf(errmsg, "out of memory");
3148 goto error;
3149 }
3150 }
3151 else if (strcmp(args[cur_arg], "on-success") == 0) {
3152 if (in_pattern) {
3153 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3154 goto error;
3155 }
3156 if (!*(args[cur_arg+1])) {
3157 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3158 goto error;
3159 }
3160 cur_arg++;
3161 on_success_msg = args[cur_arg];
3162 }
3163 else if (strcmp(args[cur_arg], "on-error") == 0) {
3164 if (in_pattern) {
3165 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3166 goto error;
3167 }
3168 if (!*(args[cur_arg+1])) {
3169 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3170 goto error;
3171 }
3172 cur_arg++;
3173 on_error_msg = args[cur_arg];
3174 }
3175 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3176 if (in_pattern) {
3177 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3178 goto error;
3179 }
3180 if (!*(args[cur_arg+1])) {
3181 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3182 goto error;
3183 }
3184 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3185 ok_st = HCHK_STATUS_L7OKD;
3186 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3187 ok_st = HCHK_STATUS_L7OKCD;
3188 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3189 ok_st = HCHK_STATUS_L6OK;
3190 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3191 ok_st = HCHK_STATUS_L4OK;
3192 else {
3193 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3194 args[cur_arg], args[cur_arg+1]);
3195 goto error;
3196 }
3197 cur_arg++;
3198 }
3199 else if (strcmp(args[cur_arg], "error-status") == 0) {
3200 if (in_pattern) {
3201 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3202 goto error;
3203 }
3204 if (!*(args[cur_arg+1])) {
3205 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3206 goto error;
3207 }
3208 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3209 err_st = HCHK_STATUS_L7RSP;
3210 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3211 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003212 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3213 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003214 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3215 err_st = HCHK_STATUS_L6RSP;
3216 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3217 err_st = HCHK_STATUS_L4CON;
3218 else {
3219 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3220 args[cur_arg], args[cur_arg+1]);
3221 goto error;
3222 }
3223 cur_arg++;
3224 }
3225 else if (strcmp(args[cur_arg], "status-code") == 0) {
3226 int idx = 0;
3227
3228 if (in_pattern) {
3229 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3230 goto error;
3231 }
3232 if (!*(args[cur_arg+1])) {
3233 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3234 goto error;
3235 }
3236
3237 cur_arg++;
3238 release_sample_expr(status_expr);
3239 px->conf.args.ctx = ARGC_SRV;
3240 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet8551c342021-09-30 16:22:51 +02003241 file, line, errmsg, (px->cap & PR_CAP_DEF) ? NULL: &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003242 if (!status_expr) {
3243 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3244 goto error;
3245 }
3246 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3247 memprintf(errmsg, "error detected while parsing status-code expression : "
3248 " fetch method '%s' extracts information from '%s', "
3249 "none of which is available here.\n",
3250 args[cur_arg], sample_src_names(status_expr->fetch->use));
3251 goto error;
3252 }
3253 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3254 }
3255 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3256 if (in_pattern) {
3257 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3258 goto error;
3259 }
3260 if (!*(args[cur_arg+1])) {
3261 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3262 goto error;
3263 }
3264 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3265 tout_st = HCHK_STATUS_L7TOUT;
3266 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3267 tout_st = HCHK_STATUS_L6TOUT;
3268 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3269 tout_st = HCHK_STATUS_L4TOUT;
3270 else {
3271 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3272 args[cur_arg], args[cur_arg+1]);
3273 goto error;
3274 }
3275 cur_arg++;
3276 }
3277 else {
3278 if (proto == TCPCHK_RULES_HTTP_CHK) {
3279 bad_http_kw:
3280 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3281 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3282 }
3283 else {
3284 bad_tcp_kw:
3285 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3286 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3287 }
3288 goto error;
3289 }
3290 next:
3291 cur_arg++;
3292 }
3293
3294 chk = calloc(1, sizeof(*chk));
3295 if (!chk) {
3296 memprintf(errmsg, "out of memory");
3297 goto error;
3298 }
3299 chk->action = TCPCHK_ACT_EXPECT;
3300 LIST_INIT(&chk->expect.onerror_fmt);
3301 LIST_INIT(&chk->expect.onsuccess_fmt);
3302 chk->comment = comment; comment = NULL;
3303 chk->expect.type = type;
3304 chk->expect.min_recv = min_recv;
3305 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3306 chk->expect.ok_status = ok_st;
3307 chk->expect.err_status = err_st;
3308 chk->expect.tout_status = tout_st;
3309 chk->expect.status_expr = status_expr; status_expr = NULL;
3310
3311 if (on_success_msg) {
3312 px->conf.args.ctx = ARGC_SRV;
3313 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3314 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3315 goto error;
3316 }
3317 }
3318 if (on_error_msg) {
3319 px->conf.args.ctx = ARGC_SRV;
3320 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3321 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3322 goto error;
3323 }
3324 }
3325
3326 switch (chk->expect.type) {
3327 case TCPCHK_EXPECT_HTTP_STATUS: {
3328 const char *p = pattern;
3329 unsigned int c1,c2;
3330
3331 chk->expect.codes.codes = NULL;
3332 chk->expect.codes.num = 0;
3333 while (1) {
3334 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3335 if (*p == '-') {
3336 p++;
3337 c2 = read_uint(&p, pattern + strlen(pattern));
3338 }
3339 if (c1 > c2) {
3340 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3341 goto error;
3342 }
3343
3344 chk->expect.codes.num++;
3345 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3346 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3347 if (!chk->expect.codes.codes) {
3348 memprintf(errmsg, "out of memory");
3349 goto error;
3350 }
3351 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3352 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3353
3354 if (*p == '\0')
3355 break;
3356 if (*p != ',') {
3357 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3358 goto error;
3359 }
3360 p++;
3361 }
3362 break;
3363 }
3364 case TCPCHK_EXPECT_STRING:
3365 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003366 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003367 if (!isttest(chk->expect.data)) {
3368 memprintf(errmsg, "out of memory");
3369 goto error;
3370 }
3371 break;
3372 case TCPCHK_EXPECT_BINARY: {
3373 int len = chk->expect.data.len;
3374
3375 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3376 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3377 goto error;
3378 }
3379 chk->expect.data.len = len;
3380 break;
3381 }
3382 case TCPCHK_EXPECT_STRING_REGEX:
3383 case TCPCHK_EXPECT_BINARY_REGEX:
3384 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3385 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3386 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3387 if (!chk->expect.regex)
3388 goto error;
3389 break;
3390
3391 case TCPCHK_EXPECT_STRING_LF:
3392 case TCPCHK_EXPECT_BINARY_LF:
3393 case TCPCHK_EXPECT_HTTP_BODY_LF:
3394 LIST_INIT(&chk->expect.fmt);
3395 px->conf.args.ctx = ARGC_SRV;
3396 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3397 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3398 goto error;
3399 }
3400 break;
3401
3402 case TCPCHK_EXPECT_HTTP_HEADER:
3403 if (!npat) {
3404 memprintf(errmsg, "unexpected error, undefined header name pattern");
3405 goto error;
3406 }
3407 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3408 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3409 if (!chk->expect.hdr.name_re)
3410 goto error;
3411 }
3412 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3413 px->conf.args.ctx = ARGC_SRV;
3414 LIST_INIT(&chk->expect.hdr.name_fmt);
3415 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3416 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3417 goto error;
3418 }
3419 }
3420 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003421 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003422 if (!isttest(chk->expect.hdr.name)) {
3423 memprintf(errmsg, "out of memory");
3424 goto error;
3425 }
3426 }
3427
3428 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3429 chk->expect.hdr.value = IST_NULL;
3430 break;
3431 }
3432
3433 if (!vpat) {
3434 memprintf(errmsg, "unexpected error, undefined header value pattern");
3435 goto error;
3436 }
3437 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3438 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3439 if (!chk->expect.hdr.value_re)
3440 goto error;
3441 }
3442 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3443 px->conf.args.ctx = ARGC_SRV;
3444 LIST_INIT(&chk->expect.hdr.value_fmt);
3445 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3446 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3447 goto error;
3448 }
3449 }
3450 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003451 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003452 if (!isttest(chk->expect.hdr.value)) {
3453 memprintf(errmsg, "out of memory");
3454 goto error;
3455 }
3456 }
3457
3458 break;
3459 case TCPCHK_EXPECT_CUSTOM:
3460 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3461 break;
3462 case TCPCHK_EXPECT_UNDEF:
3463 memprintf(errmsg, "pattern not found");
3464 goto error;
3465 }
3466
3467 /* All tcp-check expect points back to the first inverse expect rule in
3468 * a chain of one or more expect rule, potentially itself.
3469 */
3470 chk->expect.head = chk;
3471 list_for_each_entry_rev(prev_check, rules, list) {
3472 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3473 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3474 chk->expect.head = prev_check;
3475 continue;
3476 }
3477 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3478 break;
3479 }
3480 return chk;
3481
3482 error:
3483 free_tcpcheck(chk, 0);
3484 free(comment);
3485 release_sample_expr(status_expr);
3486 return NULL;
3487}
3488
3489/* Overwrites fields of the old http send rule with those of the new one. When
3490 * replaced, old values are freed and replaced by the new ones. New values are
3491 * not copied but transferred. At the end <new> should be empty and can be
3492 * safely released. This function never fails.
3493 */
3494void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3495{
3496 struct logformat_node *lf, *lfb;
3497 struct tcpcheck_http_hdr *hdr, *bhdr;
3498
3499
3500 if (new->send.http.meth.str.area) {
3501 free(old->send.http.meth.str.area);
3502 old->send.http.meth.meth = new->send.http.meth.meth;
3503 old->send.http.meth.str.area = new->send.http.meth.str.area;
3504 old->send.http.meth.str.data = new->send.http.meth.str.data;
3505 new->send.http.meth.str = BUF_NULL;
3506 }
3507
3508 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3509 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3510 istfree(&old->send.http.uri);
3511 else
3512 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3513 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3514 old->send.http.uri = new->send.http.uri;
3515 new->send.http.uri = IST_NULL;
3516 }
3517 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3518 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3519 istfree(&old->send.http.uri);
3520 else
3521 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3522 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3523 LIST_INIT(&old->send.http.uri_fmt);
3524 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003525 LIST_DELETE(&lf->list);
3526 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003527 }
3528 }
3529
3530 if (isttest(new->send.http.vsn)) {
3531 istfree(&old->send.http.vsn);
3532 old->send.http.vsn = new->send.http.vsn;
3533 new->send.http.vsn = IST_NULL;
3534 }
3535
Christopher Faulet94cd9a42022-07-05 15:33:53 +02003536 if (!LIST_ISEMPTY(&new->send.http.hdrs)) {
3537 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3538 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
3539 LIST_DELETE(&hdr->list);
3540 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
3541 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02003542 }
3543
3544 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3545 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3546 istfree(&old->send.http.body);
3547 else
3548 free_tcpcheck_fmt(&old->send.http.body_fmt);
3549 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3550 old->send.http.body = new->send.http.body;
3551 new->send.http.body = IST_NULL;
3552 }
3553 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3554 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3555 istfree(&old->send.http.body);
3556 else
3557 free_tcpcheck_fmt(&old->send.http.body_fmt);
3558 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3559 LIST_INIT(&old->send.http.body_fmt);
3560 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003561 LIST_DELETE(&lf->list);
3562 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003563 }
3564 }
3565}
3566
3567/* Internal function used to add an http-check rule in a list during the config
3568 * parsing step. Depending on its type, and the previously inserted rules, a
3569 * specific action may be performed or an error may be reported. This functions
3570 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3571 * message.
3572 */
3573int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3574{
3575 struct tcpcheck_rule *r;
3576
3577 /* the implicit send rule coming from an "option httpchk" line must be
3578 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003579 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003580 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003581 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003582 * sure the ruleset remains valid.
3583 */
3584
3585 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3586 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3587 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3588 * following tests are performed :
3589 *
3590 * 1- If there is no such rule or if it is not a send rule, the implicit send
3591 * rule is pushed in front of the ruleset
3592 *
3593 * 2- If it is another implicit send rule, it is replaced with the new one.
3594 *
3595 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3596 * both, overwriting the old send rule (the explicit one) with info of the
3597 * new send rule (the implicit one).
3598 */
3599 r = get_first_tcpcheck_rule(rules);
3600 if (r && r->action == TCPCHK_ACT_CONNECT)
3601 r = get_next_tcpcheck_rule(rules, r);
3602 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003603 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003604 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003605 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003606 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003607 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003608 }
3609 else {
3610 tcpcheck_overwrite_send_http_rule(r, chk);
3611 free_tcpcheck(chk, 0);
3612 }
3613 }
3614 else {
3615 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3616 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3617 * with an existing implicit send rule, if any. At the end, if there is no error,
3618 * the rule is appended to the list.
3619 */
3620
3621 r = get_last_tcpcheck_rule(rules);
3622 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3623 /* no error */;
3624 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3625 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3626 chk->index+1);
3627 return 0;
3628 }
3629 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3630 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3631 chk->index+1);
3632 return 0;
3633 }
3634 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3635 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3636 chk->index+1);
3637 return 0;
3638 }
3639
3640 if (chk->action == TCPCHK_ACT_SEND) {
3641 r = get_first_tcpcheck_rule(rules);
3642 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3643 tcpcheck_overwrite_send_http_rule(r, chk);
3644 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003645 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003646 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3647 chk = r;
3648 }
3649 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003650 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003651 }
3652 return 1;
3653}
3654
3655/* Check tcp-check health-check configuration for the proxy <px>. */
3656static int check_proxy_tcpcheck(struct proxy *px)
3657{
3658 struct tcpcheck_rule *chk, *back;
3659 char *comment = NULL, *errmsg = NULL;
3660 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003661 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003662
3663 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3664 deinit_proxy_tcpcheck(px);
3665 goto out;
3666 }
3667
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003668 ha_free(&px->check_command);
3669 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003670
3671 if (!px->tcpcheck_rules.list) {
3672 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
3673 ret |= ERR_ALERT | ERR_FATAL;
3674 goto out;
3675 }
3676
3677 /* HTTP ruleset only : */
3678 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3679 struct tcpcheck_rule *next;
3680
3681 /* move remaining implicit send rule from "option httpchk" line to the right place.
3682 * If such rule exists, it must be the first one. In this case, the rule is moved
3683 * after the first connect rule, if any. Otherwise, nothing is done.
3684 */
3685 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3686 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3687 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3688 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003689 LIST_DELETE(&chk->list);
3690 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003691 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003692 }
3693 }
3694
3695 /* add implicit expect rule if the last one is a send. It is inherited from previous
3696 * versions where the http expect rule was optional. Now it is possible to chained
3697 * send/expect rules but the last expect may still be implicit.
3698 */
3699 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3700 if (chk && chk->action == TCPCHK_ACT_SEND) {
3701 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3702 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3703 px->conf.file, px->conf.line, &errmsg);
3704 if (!next) {
3705 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
3706 "(%s).\n", px->id, errmsg);
3707 free(errmsg);
3708 ret |= ERR_ALERT | ERR_FATAL;
3709 goto out;
3710 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003711 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003712 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003713 }
3714 }
3715
3716 /* For all ruleset: */
3717
3718 /* If there is no connect rule preceding all send / expect rules, an
3719 * implicit one is inserted before all others.
3720 */
3721 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3722 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3723 chk = calloc(1, sizeof(*chk));
3724 if (!chk) {
3725 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
3726 "(out of memory).\n", px->id);
3727 ret |= ERR_ALERT | ERR_FATAL;
3728 goto out;
3729 }
3730 chk->action = TCPCHK_ACT_CONNECT;
3731 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003732 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003733 }
3734
3735 /* Remove all comment rules. To do so, when a such rule is found, the
3736 * comment is assigned to the following rule(s).
3737 */
3738 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003739 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3740 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003741
3742 prev_action = chk->action;
3743 switch (chk->action) {
3744 case TCPCHK_ACT_COMMENT:
3745 free(comment);
3746 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003747 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003748 free(chk);
3749 break;
3750 case TCPCHK_ACT_CONNECT:
3751 if (!chk->comment && comment)
3752 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003753 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003754 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003755 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003756 break;
3757 case TCPCHK_ACT_SEND:
3758 case TCPCHK_ACT_EXPECT:
3759 if (!chk->comment && comment)
3760 chk->comment = strdup(comment);
3761 break;
3762 }
3763 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003764 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003765
3766 out:
3767 return ret;
3768}
3769
3770void deinit_proxy_tcpcheck(struct proxy *px)
3771{
3772 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3773 px->tcpcheck_rules.flags = 0;
3774 px->tcpcheck_rules.list = NULL;
3775}
3776
3777static void deinit_tcpchecks()
3778{
3779 struct tcpcheck_ruleset *rs;
3780 struct tcpcheck_rule *r, *rb;
3781 struct ebpt_node *node, *next;
3782
3783 node = ebpt_first(&shared_tcpchecks);
3784 while (node) {
3785 next = ebpt_next(node);
3786 ebpt_delete(node);
3787 free(node->key);
3788 rs = container_of(node, typeof(*rs), node);
3789 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003790 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003791 free_tcpcheck(r, 0);
3792 }
3793 free(rs);
3794 node = next;
3795 }
3796}
3797
3798int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3799{
3800 struct tcpcheck_rule *tcpcheck, *prev_check;
3801 struct tcpcheck_expect *expect;
3802
Willy Tarreau6922e552021-03-22 21:11:45 +01003803 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003804 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003805 tcpcheck->action = TCPCHK_ACT_EXPECT;
3806
3807 expect = &tcpcheck->expect;
3808 expect->type = TCPCHK_EXPECT_STRING;
3809 LIST_INIT(&expect->onerror_fmt);
3810 LIST_INIT(&expect->onsuccess_fmt);
3811 expect->ok_status = HCHK_STATUS_L7OKD;
3812 expect->err_status = HCHK_STATUS_L7RSP;
3813 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003814 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003815 if (!isttest(expect->data)) {
3816 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3817 return 0;
3818 }
3819
3820 /* All tcp-check expect points back to the first inverse expect rule
3821 * in a chain of one or more expect rule, potentially itself.
3822 */
3823 tcpcheck->expect.head = tcpcheck;
3824 list_for_each_entry_rev(prev_check, rules->list, list) {
3825 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3826 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3827 tcpcheck->expect.head = prev_check;
3828 continue;
3829 }
3830 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3831 break;
3832 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003833 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003834 return 1;
3835}
3836
3837int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3838{
3839 struct tcpcheck_rule *tcpcheck;
3840 struct tcpcheck_send *send;
3841 const char *in;
3842 char *dst;
3843 int i;
3844
Willy Tarreau6922e552021-03-22 21:11:45 +01003845 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003846 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003847 tcpcheck->action = TCPCHK_ACT_SEND;
3848
3849 send = &tcpcheck->send;
3850 send->type = TCPCHK_SEND_STRING;
3851
3852 for (i = 0; strs[i]; i++)
3853 send->data.len += strlen(strs[i]);
3854
3855 send->data.ptr = malloc(istlen(send->data) + 1);
3856 if (!isttest(send->data)) {
3857 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3858 return 0;
3859 }
3860
3861 dst = istptr(send->data);
3862 for (i = 0; strs[i]; i++)
3863 for (in = strs[i]; (*dst = *in++); dst++);
3864 *dst = 0;
3865
Willy Tarreau2b718102021-04-21 07:32:39 +02003866 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003867 return 1;
3868}
3869
3870/* Parses the "tcp-check" proxy keyword */
3871static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003872 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003873 char **errmsg)
3874{
3875 struct tcpcheck_ruleset *rs = NULL;
3876 struct tcpcheck_rule *chk = NULL;
3877 int index, cur_arg, ret = 0;
3878
3879 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3880 ret = 1;
3881
3882 /* Deduce the ruleset name from the proxy info */
3883 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3884 ((curpx == defpx) ? "defaults" : curpx->id),
3885 curpx->conf.file, curpx->conf.line);
3886
3887 rs = find_tcpcheck_ruleset(b_orig(&trash));
3888 if (rs == NULL) {
3889 rs = create_tcpcheck_ruleset(b_orig(&trash));
3890 if (rs == NULL) {
3891 memprintf(errmsg, "out of memory.\n");
3892 goto error;
3893 }
3894 }
3895
3896 index = 0;
3897 if (!LIST_ISEMPTY(&rs->rules)) {
3898 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3899 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003900 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003901 }
3902
3903 cur_arg = 1;
3904 if (strcmp(args[cur_arg], "connect") == 0)
3905 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3906 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3907 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3908 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3909 else if (strcmp(args[cur_arg], "expect") == 0)
3910 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3911 else if (strcmp(args[cur_arg], "comment") == 0)
3912 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3913 else {
3914 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3915
3916 if (!kw) {
3917 action_kw_tcp_check_build_list(&trash);
3918 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3919 "%s%s. but got '%s'",
3920 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3921 goto error;
3922 }
3923 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3924 }
3925
3926 if (!chk) {
3927 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3928 goto error;
3929 }
3930 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3931
3932 /* No error: add the tcp-check rule in the list */
3933 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003934 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003935
3936 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3937 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3938 /* Use this ruleset if the proxy already has tcp-check enabled */
3939 curpx->tcpcheck_rules.list = &rs->rules;
3940 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3941 }
3942 else {
3943 /* mark this ruleset as unused for now */
3944 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3945 }
3946
3947 return ret;
3948
3949 error:
3950 free_tcpcheck(chk, 0);
3951 free_tcpcheck_ruleset(rs);
3952 return -1;
3953}
3954
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003955/* Parses the "http-check" proxy keyword */
3956static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003957 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003958 char **errmsg)
3959{
3960 struct tcpcheck_ruleset *rs = NULL;
3961 struct tcpcheck_rule *chk = NULL;
3962 int index, cur_arg, ret = 0;
3963
3964 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3965 ret = 1;
3966
3967 cur_arg = 1;
3968 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3969 /* enable a graceful server shutdown on an HTTP 404 response */
3970 curpx->options |= PR_O_DISABLE404;
3971 if (too_many_args(1, args, errmsg, NULL))
3972 goto error;
3973 goto out;
3974 }
3975 else if (strcmp(args[cur_arg], "send-state") == 0) {
3976 /* enable emission of the apparent state of a server in HTTP checks */
3977 curpx->options2 |= PR_O2_CHK_SNDST;
3978 if (too_many_args(1, args, errmsg, NULL))
3979 goto error;
3980 goto out;
3981 }
3982
3983 /* Deduce the ruleset name from the proxy info */
3984 chunk_printf(&trash, "*http-check-%s_%s-%d",
3985 ((curpx == defpx) ? "defaults" : curpx->id),
3986 curpx->conf.file, curpx->conf.line);
3987
3988 rs = find_tcpcheck_ruleset(b_orig(&trash));
3989 if (rs == NULL) {
3990 rs = create_tcpcheck_ruleset(b_orig(&trash));
3991 if (rs == NULL) {
3992 memprintf(errmsg, "out of memory.\n");
3993 goto error;
3994 }
3995 }
3996
3997 index = 0;
3998 if (!LIST_ISEMPTY(&rs->rules)) {
3999 chk = LIST_PREV(&rs->rules, typeof(chk), list);
4000 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4001 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004002 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004003 }
4004
4005 if (strcmp(args[cur_arg], "connect") == 0)
4006 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4007 else if (strcmp(args[cur_arg], "send") == 0)
4008 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4009 else if (strcmp(args[cur_arg], "expect") == 0)
4010 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4011 file, line, errmsg);
4012 else if (strcmp(args[cur_arg], "comment") == 0)
4013 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4014 else {
4015 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4016
4017 if (!kw) {
4018 action_kw_tcp_check_build_list(&trash);
4019 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4020 " 'send', 'expect'%s%s. but got '%s'",
4021 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4022 goto error;
4023 }
4024 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4025 }
4026
4027 if (!chk) {
4028 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4029 goto error;
4030 }
4031 ret = (*errmsg != NULL); /* Handle warning */
4032
4033 chk->index = index;
4034 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4035 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4036 /* Use this ruleset if the proxy already has http-check enabled */
4037 curpx->tcpcheck_rules.list = &rs->rules;
4038 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4039 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4040 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4041 curpx->tcpcheck_rules.list = NULL;
4042 goto error;
4043 }
4044 }
4045 else {
4046 /* mark this ruleset as unused for now */
4047 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004048 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004049 }
4050
4051 out:
4052 return ret;
4053
4054 error:
4055 free_tcpcheck(chk, 0);
4056 free_tcpcheck_ruleset(rs);
4057 return -1;
4058}
4059
4060/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004061int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004062 const char *file, int line)
4063{
4064 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4065 static char *redis_res = "+PONG\r\n";
4066
4067 struct tcpcheck_ruleset *rs = NULL;
4068 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4069 struct tcpcheck_rule *chk;
4070 char *errmsg = NULL;
4071 int err_code = 0;
4072
4073 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4074 err_code |= ERR_WARN;
4075
4076 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4077 goto out;
4078
4079 curpx->options2 &= ~PR_O2_CHK_ANY;
4080 curpx->options2 |= PR_O2_TCPCHK_CHK;
4081
4082 free_tcpcheck_vars(&rules->preset_vars);
4083 rules->list = NULL;
4084 rules->flags = 0;
4085
4086 rs = find_tcpcheck_ruleset("*redis-check");
4087 if (rs)
4088 goto ruleset_found;
4089
4090 rs = create_tcpcheck_ruleset("*redis-check");
4091 if (rs == NULL) {
4092 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4093 goto error;
4094 }
4095
4096 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4097 1, curpx, &rs->rules, file, line, &errmsg);
4098 if (!chk) {
4099 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4100 goto error;
4101 }
4102 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004103 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004104
4105 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4106 "error-status", "L7STS",
4107 "on-error", "%[res.payload(0,0),cut_crlf]",
4108 "on-success", "Redis server is ok",
4109 ""},
4110 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4111 if (!chk) {
4112 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4113 goto error;
4114 }
4115 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004116 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004117
4118 ruleset_found:
4119 rules->list = &rs->rules;
4120 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4121 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4122
4123 out:
4124 free(errmsg);
4125 return err_code;
4126
4127 error:
4128 free_tcpcheck_ruleset(rs);
4129 err_code |= ERR_ALERT | ERR_FATAL;
4130 goto out;
4131}
4132
4133
4134/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004135int 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 +01004136 const char *file, int line)
4137{
4138 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4139 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4140 *
4141 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4142 */
4143 static char sslv3_client_hello[] = {
4144 "16" /* ContentType : 0x16 = Handshake */
4145 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4146 "0079" /* ContentLength : 0x79 bytes after this one */
4147 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4148 "000075" /* HandshakeLength : 0x75 bytes after this one */
4149 "0300" /* Hello Version : 0x0300 = v3 */
4150 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4151 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4152 "00" /* Session ID length : empty (no session ID) */
4153 "004E" /* Cipher Suite Length : 78 bytes after this one */
4154 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4155 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4156 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4157 "000D" "000E" "000F" "0010" /* various bit lengths, */
4158 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4159 "0015" "0016" "0017" "0018"
4160 "0019" "001A" "001B" "002F"
4161 "0030" "0031" "0032" "0033"
4162 "0034" "0035" "0036" "0037"
4163 "0038" "0039" "003A"
4164 "01" /* Compression Length : 0x01 = 1 byte for types */
4165 "00" /* Compression Type : 0x00 = NULL compression */
4166 };
4167
4168 struct tcpcheck_ruleset *rs = NULL;
4169 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4170 struct tcpcheck_rule *chk;
4171 char *errmsg = NULL;
4172 int err_code = 0;
4173
4174 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4175 err_code |= ERR_WARN;
4176
4177 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4178 goto out;
4179
4180 curpx->options2 &= ~PR_O2_CHK_ANY;
4181 curpx->options2 |= PR_O2_TCPCHK_CHK;
4182
4183 free_tcpcheck_vars(&rules->preset_vars);
4184 rules->list = NULL;
4185 rules->flags = 0;
4186
4187 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4188 if (rs)
4189 goto ruleset_found;
4190
4191 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4192 if (rs == NULL) {
4193 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4194 goto error;
4195 }
4196
4197 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4198 1, curpx, &rs->rules, file, line, &errmsg);
4199 if (!chk) {
4200 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4201 goto error;
4202 }
4203 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004204 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004205
4206 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4207 "min-recv", "5", "ok-status", "L6OK",
4208 "error-status", "L6RSP", "tout-status", "L6TOUT",
4209 ""},
4210 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4211 if (!chk) {
4212 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4213 goto error;
4214 }
4215 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004216 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004217
4218 ruleset_found:
4219 rules->list = &rs->rules;
4220 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4221 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4222
4223 out:
4224 free(errmsg);
4225 return err_code;
4226
4227 error:
4228 free_tcpcheck_ruleset(rs);
4229 err_code |= ERR_ALERT | ERR_FATAL;
4230 goto out;
4231}
4232
4233/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004234int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004235 const char *file, int line)
4236{
4237 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4238
4239 struct tcpcheck_ruleset *rs = NULL;
4240 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4241 struct tcpcheck_rule *chk;
4242 struct tcpcheck_var *var = NULL;
4243 char *cmd = NULL, *errmsg = NULL;
4244 int err_code = 0;
4245
4246 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4247 err_code |= ERR_WARN;
4248
4249 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4250 goto out;
4251
4252 curpx->options2 &= ~PR_O2_CHK_ANY;
4253 curpx->options2 |= PR_O2_TCPCHK_CHK;
4254
4255 free_tcpcheck_vars(&rules->preset_vars);
4256 rules->list = NULL;
4257 rules->flags = 0;
4258
4259 cur_arg += 2;
4260 if (*args[cur_arg] && *args[cur_arg+1] &&
4261 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4262 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4263 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4264 if (cmd)
4265 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4266 }
4267 else {
4268 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4269 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4270 cmd = strdup("HELO localhost");
4271 }
4272
4273 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4274 if (cmd == NULL || var == NULL) {
4275 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4276 goto error;
4277 }
4278 var->data.type = SMP_T_STR;
4279 var->data.u.str.area = cmd;
4280 var->data.u.str.data = strlen(cmd);
4281 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004282 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004283 cmd = NULL;
4284 var = NULL;
4285
4286 rs = find_tcpcheck_ruleset("*smtp-check");
4287 if (rs)
4288 goto ruleset_found;
4289
4290 rs = create_tcpcheck_ruleset("*smtp-check");
4291 if (rs == NULL) {
4292 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4293 goto error;
4294 }
4295
4296 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4297 1, curpx, &rs->rules, file, line, &errmsg);
4298 if (!chk) {
4299 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4300 goto error;
4301 }
4302 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004303 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004304
4305 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4306 "min-recv", "4",
4307 "error-status", "L7RSP",
4308 "on-error", "%[res.payload(0,0),cut_crlf]",
4309 ""},
4310 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4311 if (!chk) {
4312 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4313 goto error;
4314 }
4315 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004316 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004317
4318 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4319 "min-recv", "4",
4320 "error-status", "L7STS",
4321 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4322 "status-code", "res.payload(0,3)",
4323 ""},
4324 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4325 if (!chk) {
4326 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4327 goto error;
4328 }
4329 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004330 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004331
4332 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4333 1, curpx, &rs->rules, file, line, &errmsg);
4334 if (!chk) {
4335 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4336 goto error;
4337 }
4338 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004339 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004340
4341 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4342 "min-recv", "4",
4343 "error-status", "L7STS",
4344 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4345 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4346 "status-code", "res.payload(0,3)",
4347 ""},
4348 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4349 if (!chk) {
4350 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4351 goto error;
4352 }
4353 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004354 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004355
4356 ruleset_found:
4357 rules->list = &rs->rules;
4358 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4359 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4360
4361 out:
4362 free(errmsg);
4363 return err_code;
4364
4365 error:
4366 free(cmd);
4367 free(var);
4368 free_tcpcheck_vars(&rules->preset_vars);
4369 free_tcpcheck_ruleset(rs);
4370 err_code |= ERR_ALERT | ERR_FATAL;
4371 goto out;
4372}
4373
4374/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004375int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004376 const char *file, int line)
4377{
4378 static char pgsql_req[] = {
4379 "%[var(check.plen),htonl,hex]" /* The packet length*/
4380 "00030000" /* the version 3.0 */
4381 "7573657200" /* "user" key */
4382 "%[var(check.username),hex]00" /* the username */
4383 "00"
4384 };
4385
4386 struct tcpcheck_ruleset *rs = NULL;
4387 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4388 struct tcpcheck_rule *chk;
4389 struct tcpcheck_var *var = NULL;
4390 char *user = NULL, *errmsg = NULL;
4391 size_t packetlen = 0;
4392 int err_code = 0;
4393
4394 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4395 err_code |= ERR_WARN;
4396
4397 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4398 goto out;
4399
4400 curpx->options2 &= ~PR_O2_CHK_ANY;
4401 curpx->options2 |= PR_O2_TCPCHK_CHK;
4402
4403 free_tcpcheck_vars(&rules->preset_vars);
4404 rules->list = NULL;
4405 rules->flags = 0;
4406
4407 cur_arg += 2;
4408 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4409 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4410 file, line, args[0], args[1]);
4411 goto error;
4412 }
4413 if (strcmp(args[cur_arg], "user") == 0) {
4414 packetlen = 15 + strlen(args[cur_arg+1]);
4415 user = strdup(args[cur_arg+1]);
4416
4417 var = create_tcpcheck_var(ist("check.username"));
4418 if (user == NULL || var == NULL) {
4419 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4420 goto error;
4421 }
4422 var->data.type = SMP_T_STR;
4423 var->data.u.str.area = user;
4424 var->data.u.str.data = strlen(user);
4425 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004426 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004427 user = NULL;
4428 var = NULL;
4429
4430 var = create_tcpcheck_var(ist("check.plen"));
4431 if (var == NULL) {
4432 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4433 goto error;
4434 }
4435 var->data.type = SMP_T_SINT;
4436 var->data.u.sint = packetlen;
4437 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004438 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004439 var = NULL;
4440 }
4441 else {
4442 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4443 file, line, args[0], args[1]);
4444 goto error;
4445 }
4446
4447 rs = find_tcpcheck_ruleset("*pgsql-check");
4448 if (rs)
4449 goto ruleset_found;
4450
4451 rs = create_tcpcheck_ruleset("*pgsql-check");
4452 if (rs == NULL) {
4453 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4454 goto error;
4455 }
4456
4457 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4458 1, curpx, &rs->rules, file, line, &errmsg);
4459 if (!chk) {
4460 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4461 goto error;
4462 }
4463 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004464 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004465
4466 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4467 1, curpx, &rs->rules, file, line, &errmsg);
4468 if (!chk) {
4469 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4470 goto error;
4471 }
4472 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004473 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004474
4475 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4476 "min-recv", "5",
4477 "error-status", "L7RSP",
4478 "on-error", "%[res.payload(6,0)]",
4479 ""},
4480 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4481 if (!chk) {
4482 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4483 goto error;
4484 }
4485 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004486 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004487
4488 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4489 "min-recv", "9",
4490 "error-status", "L7STS",
4491 "on-success", "PostgreSQL server is ok",
4492 "on-error", "PostgreSQL unknown error",
4493 ""},
4494 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4495 if (!chk) {
4496 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4497 goto error;
4498 }
4499 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004500 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004501
4502 ruleset_found:
4503 rules->list = &rs->rules;
4504 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4505 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4506
4507 out:
4508 free(errmsg);
4509 return err_code;
4510
4511 error:
4512 free(user);
4513 free(var);
4514 free_tcpcheck_vars(&rules->preset_vars);
4515 free_tcpcheck_ruleset(rs);
4516 err_code |= ERR_ALERT | ERR_FATAL;
4517 goto out;
4518}
4519
4520
4521/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004522int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004523 const char *file, int line)
4524{
4525 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4526 * const char mysql40_client_auth_pkt[] = {
4527 * "\x0e\x00\x00" // packet length
4528 * "\x01" // packet number
4529 * "\x00\x00" // client capabilities
4530 * "\x00\x00\x01" // max packet
4531 * "haproxy\x00" // username (null terminated string)
4532 * "\x00" // filler (always 0x00)
4533 * "\x01\x00\x00" // packet length
4534 * "\x00" // packet number
4535 * "\x01" // COM_QUIT command
4536 * };
4537 */
4538 static char mysql40_rsname[] = "*mysql40-check";
4539 static char mysql40_req[] = {
4540 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4541 "0080" /* client capabilities */
4542 "000001" /* max packet */
4543 "%[var(check.username),hex]00" /* the username */
4544 "00" /* filler (always 0x00) */
4545 "010000" /* packet length*/
4546 "00" /* sequence ID */
4547 "01" /* COM_QUIT command */
4548 };
4549
4550 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4551 * const char mysql41_client_auth_pkt[] = {
4552 * "\x0e\x00\x00\" // packet length
4553 * "\x01" // packet number
4554 * "\x00\x00\x00\x00" // client capabilities
4555 * "\x00\x00\x00\x01" // max packet
4556 * "\x21" // character set (UTF-8)
4557 * char[23] // All zeroes
4558 * "haproxy\x00" // username (null terminated string)
4559 * "\x00" // filler (always 0x00)
4560 * "\x01\x00\x00" // packet length
4561 * "\x00" // packet number
4562 * "\x01" // COM_QUIT command
4563 * };
4564 */
4565 static char mysql41_rsname[] = "*mysql41-check";
4566 static char mysql41_req[] = {
4567 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4568 "00820000" /* client capabilities */
4569 "00800001" /* max packet */
4570 "21" /* character set (UTF-8) */
4571 "000000000000000000000000" /* 23 bytes, al zeroes */
4572 "0000000000000000000000"
4573 "%[var(check.username),hex]00" /* the username */
4574 "00" /* filler (always 0x00) */
4575 "010000" /* packet length*/
4576 "00" /* sequence ID */
4577 "01" /* COM_QUIT command */
4578 };
4579
4580 struct tcpcheck_ruleset *rs = NULL;
4581 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4582 struct tcpcheck_rule *chk;
4583 struct tcpcheck_var *var = NULL;
4584 char *mysql_rsname = "*mysql-check";
4585 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4586 int index = 0, err_code = 0;
4587
4588 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4589 err_code |= ERR_WARN;
4590
4591 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4592 goto out;
4593
4594 curpx->options2 &= ~PR_O2_CHK_ANY;
4595 curpx->options2 |= PR_O2_TCPCHK_CHK;
4596
4597 free_tcpcheck_vars(&rules->preset_vars);
4598 rules->list = NULL;
4599 rules->flags = 0;
4600
4601 cur_arg += 2;
4602 if (*args[cur_arg]) {
4603 int packetlen, userlen;
4604
4605 if (strcmp(args[cur_arg], "user") != 0) {
4606 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4607 file, line, args[0], args[1], args[cur_arg]);
4608 goto error;
4609 }
4610
4611 if (*(args[cur_arg+1]) == 0) {
4612 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4613 file, line, args[0], args[1], args[cur_arg]);
4614 goto error;
4615 }
4616
4617 hdr = calloc(4, sizeof(*hdr));
4618 user = strdup(args[cur_arg+1]);
4619 userlen = strlen(args[cur_arg+1]);
4620
4621 if (hdr == NULL || user == NULL) {
4622 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4623 goto error;
4624 }
4625
4626 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4627 packetlen = userlen + 7 + 27;
4628 mysql_req = mysql41_req;
4629 mysql_rsname = mysql41_rsname;
4630 }
4631 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4632 packetlen = userlen + 7;
4633 mysql_req = mysql40_req;
4634 mysql_rsname = mysql40_rsname;
4635 }
4636 else {
4637 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4638 file, line, args[cur_arg], args[cur_arg+2]);
4639 goto error;
4640 }
4641
4642 hdr[0] = (unsigned char)(packetlen & 0xff);
4643 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4644 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4645 hdr[3] = 1;
4646
4647 var = create_tcpcheck_var(ist("check.header"));
4648 if (var == NULL) {
4649 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4650 goto error;
4651 }
4652 var->data.type = SMP_T_STR;
4653 var->data.u.str.area = hdr;
4654 var->data.u.str.data = 4;
4655 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004656 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004657 hdr = NULL;
4658 var = NULL;
4659
4660 var = create_tcpcheck_var(ist("check.username"));
4661 if (var == NULL) {
4662 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4663 goto error;
4664 }
4665 var->data.type = SMP_T_STR;
4666 var->data.u.str.area = user;
4667 var->data.u.str.data = strlen(user);
4668 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004669 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004670 user = NULL;
4671 var = NULL;
4672 }
4673
4674 rs = find_tcpcheck_ruleset(mysql_rsname);
4675 if (rs)
4676 goto ruleset_found;
4677
4678 rs = create_tcpcheck_ruleset(mysql_rsname);
4679 if (rs == NULL) {
4680 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4681 goto error;
4682 }
4683
4684 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4685 1, curpx, &rs->rules, file, line, &errmsg);
4686 if (!chk) {
4687 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4688 goto error;
4689 }
4690 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004691 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004692
4693 if (mysql_req) {
4694 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4695 1, curpx, &rs->rules, file, line, &errmsg);
4696 if (!chk) {
4697 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4698 goto error;
4699 }
4700 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004701 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004702 }
4703
4704 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4705 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4706 if (!chk) {
4707 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4708 goto error;
4709 }
4710 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4711 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004712 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004713
4714 if (mysql_req) {
4715 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4716 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4717 if (!chk) {
4718 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4719 goto error;
4720 }
4721 chk->expect.custom = tcpcheck_mysql_expect_ok;
4722 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004723 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004724 }
4725
4726 ruleset_found:
4727 rules->list = &rs->rules;
4728 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4729 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4730
4731 out:
4732 free(errmsg);
4733 return err_code;
4734
4735 error:
4736 free(hdr);
4737 free(user);
4738 free(var);
4739 free_tcpcheck_vars(&rules->preset_vars);
4740 free_tcpcheck_ruleset(rs);
4741 err_code |= ERR_ALERT | ERR_FATAL;
4742 goto out;
4743}
4744
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004745int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004746 const char *file, int line)
4747{
4748 static char *ldap_req = "300C020101600702010304008000";
4749
4750 struct tcpcheck_ruleset *rs = NULL;
4751 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4752 struct tcpcheck_rule *chk;
4753 char *errmsg = NULL;
4754 int err_code = 0;
4755
4756 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4757 err_code |= ERR_WARN;
4758
4759 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4760 goto out;
4761
4762 curpx->options2 &= ~PR_O2_CHK_ANY;
4763 curpx->options2 |= PR_O2_TCPCHK_CHK;
4764
4765 free_tcpcheck_vars(&rules->preset_vars);
4766 rules->list = NULL;
4767 rules->flags = 0;
4768
4769 rs = find_tcpcheck_ruleset("*ldap-check");
4770 if (rs)
4771 goto ruleset_found;
4772
4773 rs = create_tcpcheck_ruleset("*ldap-check");
4774 if (rs == NULL) {
4775 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4776 goto error;
4777 }
4778
4779 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4780 1, curpx, &rs->rules, file, line, &errmsg);
4781 if (!chk) {
4782 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4783 goto error;
4784 }
4785 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004786 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004787
4788 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4789 "min-recv", "14",
4790 "on-error", "Not LDAPv3 protocol",
4791 ""},
4792 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4793 if (!chk) {
4794 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4795 goto error;
4796 }
4797 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004798 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004799
4800 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4801 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4802 if (!chk) {
4803 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4804 goto error;
4805 }
4806 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4807 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004808 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004809
4810 ruleset_found:
4811 rules->list = &rs->rules;
4812 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4813 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4814
4815 out:
4816 free(errmsg);
4817 return err_code;
4818
4819 error:
4820 free_tcpcheck_ruleset(rs);
4821 err_code |= ERR_ALERT | ERR_FATAL;
4822 goto out;
4823}
4824
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004825int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004826 const char *file, int line)
4827{
4828 struct tcpcheck_ruleset *rs = NULL;
4829 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4830 struct tcpcheck_rule *chk;
4831 char *spop_req = NULL;
4832 char *errmsg = NULL;
4833 int spop_len = 0, err_code = 0;
4834
4835 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4836 err_code |= ERR_WARN;
4837
4838 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4839 goto out;
4840
4841 curpx->options2 &= ~PR_O2_CHK_ANY;
4842 curpx->options2 |= PR_O2_TCPCHK_CHK;
4843
4844 free_tcpcheck_vars(&rules->preset_vars);
4845 rules->list = NULL;
4846 rules->flags = 0;
4847
4848
4849 rs = find_tcpcheck_ruleset("*spop-check");
4850 if (rs)
4851 goto ruleset_found;
4852
4853 rs = create_tcpcheck_ruleset("*spop-check");
4854 if (rs == NULL) {
4855 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4856 goto error;
4857 }
4858
4859 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4860 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4861 goto error;
4862 }
4863 chunk_reset(&trash);
4864 dump_binary(&trash, spop_req, spop_len);
4865 trash.area[trash.data] = '\0';
4866
4867 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4868 1, curpx, &rs->rules, file, line, &errmsg);
4869 if (!chk) {
4870 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4871 goto error;
4872 }
4873 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004874 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004875
4876 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4877 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4878 if (!chk) {
4879 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4880 goto error;
4881 }
4882 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4883 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004884 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004885
4886 ruleset_found:
4887 rules->list = &rs->rules;
4888 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4889 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4890
4891 out:
4892 free(spop_req);
4893 free(errmsg);
4894 return err_code;
4895
4896 error:
4897 free_tcpcheck_ruleset(rs);
4898 err_code |= ERR_ALERT | ERR_FATAL;
4899 goto out;
4900}
4901
4902
4903static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4904{
4905 struct tcpcheck_rule *chk = NULL;
4906 struct tcpcheck_http_hdr *hdr = NULL;
4907 char *meth = NULL, *uri = NULL, *vsn = NULL;
4908 char *hdrs, *body;
4909
4910 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4911 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4912 if (hdrs == body)
4913 hdrs = NULL;
4914 if (hdrs) {
4915 *hdrs = '\0';
4916 hdrs +=2;
4917 }
4918 if (body) {
4919 *body = '\0';
4920 body += 4;
4921 }
4922 if (hdrs || body) {
4923 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4924 " Please, consider to use 'http-check send' directive instead.");
4925 }
4926
4927 chk = calloc(1, sizeof(*chk));
4928 if (!chk) {
4929 memprintf(errmsg, "out of memory");
4930 goto error;
4931 }
4932 chk->action = TCPCHK_ACT_SEND;
4933 chk->send.type = TCPCHK_SEND_HTTP;
4934 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4935 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4936 LIST_INIT(&chk->send.http.hdrs);
4937
4938 /* Copy the method, uri and version */
4939 if (*args[cur_arg]) {
4940 if (!*args[cur_arg+1])
4941 uri = args[cur_arg];
4942 else
4943 meth = args[cur_arg];
4944 }
4945 if (*args[cur_arg+1])
4946 uri = args[cur_arg+1];
4947 if (*args[cur_arg+2])
4948 vsn = args[cur_arg+2];
4949
4950 if (meth) {
4951 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4952 chk->send.http.meth.str.area = strdup(meth);
4953 chk->send.http.meth.str.data = strlen(meth);
4954 if (!chk->send.http.meth.str.area) {
4955 memprintf(errmsg, "out of memory");
4956 goto error;
4957 }
4958 }
4959 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004960 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004961 if (!isttest(chk->send.http.uri)) {
4962 memprintf(errmsg, "out of memory");
4963 goto error;
4964 }
4965 }
4966 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004967 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004968 if (!isttest(chk->send.http.vsn)) {
4969 memprintf(errmsg, "out of memory");
4970 goto error;
4971 }
4972 }
4973
4974 /* Copy the header */
4975 if (hdrs) {
4976 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4977 struct h1m h1m;
4978 int i, ret;
4979
4980 /* Build and parse the request */
4981 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4982
4983 h1m.flags = H1_MF_HDRS_ONLY;
4984 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4985 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4986 &h1m, NULL);
4987 if (ret <= 0) {
4988 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4989 goto error;
4990 }
4991
4992 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4993 hdr = calloc(1, sizeof(*hdr));
4994 if (!hdr) {
4995 memprintf(errmsg, "out of memory");
4996 goto error;
4997 }
4998 LIST_INIT(&hdr->value);
4999 hdr->name = istdup(tmp_hdrs[i].n);
5000 if (!hdr->name.ptr) {
5001 memprintf(errmsg, "out of memory");
5002 goto error;
5003 }
5004
5005 ist0(tmp_hdrs[i].v);
5006 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5007 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005008 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005009 }
5010 }
5011
5012 /* Copy the body */
5013 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005014 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005015 if (!isttest(chk->send.http.body)) {
5016 memprintf(errmsg, "out of memory");
5017 goto error;
5018 }
5019 }
5020
5021 return chk;
5022
5023 error:
5024 free_tcpcheck_http_hdr(hdr);
5025 free_tcpcheck(chk, 0);
5026 return NULL;
5027}
5028
5029/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005030int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005031 const char *file, int line)
5032{
5033 struct tcpcheck_ruleset *rs = NULL;
5034 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5035 struct tcpcheck_rule *chk;
5036 char *errmsg = NULL;
5037 int err_code = 0;
5038
5039 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5040 err_code |= ERR_WARN;
5041
5042 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5043 goto out;
5044
5045 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5046 if (!chk) {
5047 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5048 goto error;
5049 }
5050 if (errmsg) {
5051 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5052 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005053 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005054 }
5055
5056 no_request:
5057 curpx->options2 &= ~PR_O2_CHK_ANY;
5058 curpx->options2 |= PR_O2_TCPCHK_CHK;
5059
5060 free_tcpcheck_vars(&rules->preset_vars);
5061 rules->list = NULL;
5062 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5063
5064 /* Deduce the ruleset name from the proxy info */
5065 chunk_printf(&trash, "*http-check-%s_%s-%d",
5066 ((curpx == defpx) ? "defaults" : curpx->id),
5067 curpx->conf.file, curpx->conf.line);
5068
5069 rs = find_tcpcheck_ruleset(b_orig(&trash));
5070 if (rs == NULL) {
5071 rs = create_tcpcheck_ruleset(b_orig(&trash));
5072 if (rs == NULL) {
5073 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5074 goto error;
5075 }
5076 }
5077
5078 rules->list = &rs->rules;
5079 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5080 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5081 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5082 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5083 rules->list = NULL;
5084 goto error;
5085 }
5086
5087 out:
5088 free(errmsg);
5089 return err_code;
5090
5091 error:
5092 free_tcpcheck_ruleset(rs);
5093 free_tcpcheck(chk, 0);
5094 err_code |= ERR_ALERT | ERR_FATAL;
5095 goto out;
5096}
5097
5098/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005099int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005100 const char *file, int line)
5101{
5102 struct tcpcheck_ruleset *rs = NULL;
5103 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5104 int err_code = 0;
5105
5106 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5107 err_code |= ERR_WARN;
5108
5109 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5110 goto out;
5111
5112 curpx->options2 &= ~PR_O2_CHK_ANY;
5113 curpx->options2 |= PR_O2_TCPCHK_CHK;
5114
5115 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5116 /* If a tcp-check rulesset is already set, do nothing */
5117 if (rules->list)
5118 goto out;
5119
5120 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5121 * get it.
5122 */
5123 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5124 goto curpx_ruleset;
5125
5126 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5127 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5128 rs = find_tcpcheck_ruleset(b_orig(&trash));
5129 if (rs)
5130 goto ruleset_found;
5131 }
5132
5133 curpx_ruleset:
5134 /* Deduce the ruleset name from the proxy info */
5135 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5136 ((curpx == defpx) ? "defaults" : curpx->id),
5137 curpx->conf.file, curpx->conf.line);
5138
5139 rs = find_tcpcheck_ruleset(b_orig(&trash));
5140 if (rs == NULL) {
5141 rs = create_tcpcheck_ruleset(b_orig(&trash));
5142 if (rs == NULL) {
5143 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5144 goto error;
5145 }
5146 }
5147
5148 ruleset_found:
5149 free_tcpcheck_vars(&rules->preset_vars);
5150 rules->list = &rs->rules;
5151 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5152 rules->flags |= TCPCHK_RULES_TCP_CHK;
5153
5154 out:
5155 return err_code;
5156
5157 error:
5158 err_code |= ERR_ALERT | ERR_FATAL;
5159 goto out;
5160}
5161
Willy Tarreau51cd5952020-06-05 12:25:38 +02005162static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005163 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005164 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5165 { 0, NULL, NULL },
5166}};
5167
5168REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5169REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5170REGISTER_POST_DEINIT(deinit_tcpchecks);
5171INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);