blob: 1c20f5eebbf8a22b9b82844f2f7a7cceb6bc4aac [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) {
1597 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1598 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001599 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001600 goto wait_more_data;
1601 }
1602 if (is_empty) {
1603 int status;
1604
1605 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1606 tcpcheck_get_step_id(check, rule));
1607 if (rule->comment)
1608 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1609
Christopher Faulet147b8c92021-04-10 09:00:38 +02001610 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001611 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1612 set_server_check_status(check, status, trash.area);
1613 goto stop;
1614 }
1615 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001616 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001617
1618 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001619 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1620 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001621
1622 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001623 return ret;
1624
1625 stop:
1626 ret = TCPCHK_EVAL_STOP;
1627 goto out;
1628
1629 wait_more_data:
1630 ret = TCPCHK_EVAL_WAIT;
1631 goto out;
1632}
1633
1634/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1635 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1636 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1637 * error occurred.
1638 */
1639enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1640{
1641 struct htx *htx = htxbuf(&check->bi);
1642 struct htx_sl *sl;
1643 struct htx_blk *blk;
1644 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1645 struct tcpcheck_expect *expect = &rule->expect;
1646 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1647 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1648 struct ist desc = IST_NULL;
1649 int i, match, inverse;
1650
Christopher Faulet147b8c92021-04-10 09:00:38 +02001651 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1652
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001653 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001654
1655 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001656 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001657 status = HCHK_STATUS_L7RSP;
1658 goto error;
1659 }
1660
1661 if (htx_is_empty(htx)) {
1662 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001663 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001664 status = HCHK_STATUS_L7RSP;
1665 goto error;
1666 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001667 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001668 goto wait_more_data;
1669 }
1670
1671 sl = http_get_stline(htx);
1672 check->code = sl->info.res.status;
1673
1674 if (check->server &&
1675 (check->server->proxy->options & PR_O_DISABLE404) &&
1676 (check->server->next_state != SRV_ST_STOPPED) &&
1677 (check->code == 404)) {
1678 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001679 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001680 goto out;
1681 }
1682
1683 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1684 /* Make GCC happy ; initialize match to a failure state. */
1685 match = inverse;
1686 status = expect->err_status;
1687
1688 switch (expect->type) {
1689 case TCPCHK_EXPECT_HTTP_STATUS:
1690 match = 0;
1691 for (i = 0; i < expect->codes.num; i++) {
1692 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1693 sl->info.res.status <= expect->codes.codes[i][1]) {
1694 match = 1;
1695 break;
1696 }
1697 }
1698
1699 /* Set status and description in case of error */
1700 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1701 if (LIST_ISEMPTY(&expect->onerror_fmt))
1702 desc = htx_sl_res_reason(sl);
1703 break;
1704 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1705 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1706
1707 /* Set status and description in case of error */
1708 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1709 if (LIST_ISEMPTY(&expect->onerror_fmt))
1710 desc = htx_sl_res_reason(sl);
1711 break;
1712
1713 case TCPCHK_EXPECT_HTTP_HEADER: {
1714 struct http_hdr_ctx ctx;
1715 struct ist npat, vpat, value;
1716 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1717
1718 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1719 nbuf = alloc_trash_chunk();
1720 if (!nbuf) {
1721 status = HCHK_STATUS_L7RSP;
1722 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001723 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001724 goto error;
1725 }
1726 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1727 if (!b_data(nbuf)) {
1728 status = HCHK_STATUS_L7RSP;
1729 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001730 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001731 goto error;
1732 }
1733 npat = ist2(b_orig(nbuf), b_data(nbuf));
1734 }
1735 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1736 npat = expect->hdr.name;
1737
1738 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1739 vbuf = alloc_trash_chunk();
1740 if (!vbuf) {
1741 status = HCHK_STATUS_L7RSP;
1742 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001743 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001744 goto error;
1745 }
1746 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1747 if (!b_data(vbuf)) {
1748 status = HCHK_STATUS_L7RSP;
1749 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001750 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001751 goto error;
1752 }
1753 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1754 }
1755 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1756 vpat = expect->hdr.value;
1757
1758 match = 0;
1759 ctx.blk = NULL;
1760 while (1) {
1761 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1762 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1763 if (!http_find_str_header(htx, npat, &ctx, full))
1764 goto end_of_match;
1765 break;
1766 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1767 if (!http_find_pfx_header(htx, npat, &ctx, full))
1768 goto end_of_match;
1769 break;
1770 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1771 if (!http_find_sfx_header(htx, npat, &ctx, full))
1772 goto end_of_match;
1773 break;
1774 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1775 if (!http_find_sub_header(htx, npat, &ctx, full))
1776 goto end_of_match;
1777 break;
1778 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1779 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1780 goto end_of_match;
1781 break;
1782 default:
1783 /* should never happen */
1784 goto end_of_match;
1785 }
1786
1787 /* A header has matched the name pattern, let's test its
1788 * value now (always defined from there). If there is no
1789 * value pattern, it is a good match.
1790 */
1791
1792 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1793 match = 1;
1794 goto end_of_match;
1795 }
1796
1797 value = ctx.value;
1798 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1799 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1800 if (isteq(value, vpat)) {
1801 match = 1;
1802 goto end_of_match;
1803 }
1804 break;
1805 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1806 if (istlen(value) < istlen(vpat))
1807 break;
1808 value = ist2(istptr(value), istlen(vpat));
1809 if (isteq(value, vpat)) {
1810 match = 1;
1811 goto end_of_match;
1812 }
1813 break;
1814 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1815 if (istlen(value) < istlen(vpat))
1816 break;
1817 value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
1818 if (isteq(value, vpat)) {
1819 match = 1;
1820 goto end_of_match;
1821 }
1822 break;
1823 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1824 if (isttest(istist(value, vpat))) {
1825 match = 1;
1826 goto end_of_match;
1827 }
1828 break;
1829 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1830 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1831 match = 1;
1832 goto end_of_match;
1833 }
1834 break;
1835 }
1836 }
1837
1838 end_of_match:
1839 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1840 if (LIST_ISEMPTY(&expect->onerror_fmt))
1841 desc = htx_sl_res_reason(sl);
1842 break;
1843 }
1844
1845 case TCPCHK_EXPECT_HTTP_BODY:
1846 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1847 case TCPCHK_EXPECT_HTTP_BODY_LF:
1848 match = 0;
1849 chunk_reset(&trash);
1850 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1851 enum htx_blk_type type = htx_get_blk_type(blk);
1852
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001853 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001854 break;
1855 if (type == HTX_BLK_DATA) {
1856 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1857 break;
1858 }
1859 }
1860
1861 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001862 if (!last_read) {
1863 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001864 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001865 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001866 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1867 if (LIST_ISEMPTY(&expect->onerror_fmt))
1868 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001869 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001870 goto error;
1871 }
1872
1873 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1874 tmp = alloc_trash_chunk();
1875 if (!tmp) {
1876 status = HCHK_STATUS_L7RSP;
1877 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001878 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001879 goto error;
1880 }
1881 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1882 if (!b_data(tmp)) {
1883 status = HCHK_STATUS_L7RSP;
1884 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001885 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001886 goto error;
1887 }
1888 }
1889
1890 if (!last_read &&
1891 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1892 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1893 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1894 ret = TCPCHK_EVAL_WAIT;
1895 goto out;
1896 }
1897
1898 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1899 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1900 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1901 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1902 else
1903 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1904
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001905 /* Wait for more data on mismatch only if no minimum is defined (-1),
1906 * otherwise the absence of match is already conclusive.
1907 */
1908 if (!match && !last_read && (expect->min_recv == -1)) {
1909 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001910 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001911 goto out;
1912 }
1913
Willy Tarreau51cd5952020-06-05 12:25:38 +02001914 /* Set status and description in case of error */
1915 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1916 if (LIST_ISEMPTY(&expect->onerror_fmt))
1917 desc = (inverse
1918 ? ist("HTTP check matched unwanted content")
1919 : ist("HTTP content check did not match"));
1920 break;
1921
1922
1923 default:
1924 /* should never happen */
1925 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1926 goto error;
1927 }
1928
Christopher Faulet147b8c92021-04-10 09:00:38 +02001929 if (!(match ^ inverse)) {
1930 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001931 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001932 }
1933
1934 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001935
1936 out:
1937 free_trash_chunk(tmp);
1938 free_trash_chunk(nbuf);
1939 free_trash_chunk(vbuf);
1940 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001941 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001942 return ret;
1943
1944 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001945 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001946 ret = TCPCHK_EVAL_STOP;
1947 msg = alloc_trash_chunk();
1948 if (msg)
1949 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1950 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1951 goto out;
1952
1953 wait_more_data:
1954 ret = TCPCHK_EVAL_WAIT;
1955 goto out;
1956}
1957
1958/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1959 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1960 * if an error occurred.
1961 */
1962enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1963{
1964 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1965 struct tcpcheck_expect *expect = &rule->expect;
1966 struct buffer *msg = NULL, *tmp = NULL;
1967 struct ist desc = IST_NULL;
1968 enum healthcheck_status status;
1969 int match, inverse;
1970
Christopher Faulet147b8c92021-04-10 09:00:38 +02001971 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1972
Willy Tarreau51cd5952020-06-05 12:25:38 +02001973 last_read |= b_full(&check->bi);
1974
1975 /* The current expect might need more data than the previous one, check again
1976 * that the minimum amount data required to match is respected.
1977 */
1978 if (!last_read) {
1979 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1980 (b_data(&check->bi) < istlen(expect->data))) {
1981 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001982 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001983 goto out;
1984 }
1985 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1986 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001987 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001988 goto out;
1989 }
1990 }
1991
1992 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1993 /* Make GCC happy ; initialize match to a failure state. */
1994 match = inverse;
1995 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1996
1997 switch (expect->type) {
1998 case TCPCHK_EXPECT_STRING:
1999 case TCPCHK_EXPECT_BINARY:
2000 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2001 break;
2002 case TCPCHK_EXPECT_STRING_REGEX:
2003 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2004 break;
2005
2006 case TCPCHK_EXPECT_BINARY_REGEX:
2007 chunk_reset(&trash);
2008 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2009 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2010 break;
2011
2012 case TCPCHK_EXPECT_STRING_LF:
2013 case TCPCHK_EXPECT_BINARY_LF:
2014 match = 0;
2015 tmp = alloc_trash_chunk();
2016 if (!tmp) {
2017 status = HCHK_STATUS_L7RSP;
2018 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002019 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002020 goto error;
2021 }
2022 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2023 if (!b_data(tmp)) {
2024 status = HCHK_STATUS_L7RSP;
2025 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002026 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002027 goto error;
2028 }
2029 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2030 int len = tmp->data;
2031 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2032 status = HCHK_STATUS_L7RSP;
2033 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002034 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002035 goto error;
2036 }
2037 tmp->data = len;
2038 }
2039 if (b_data(&check->bi) < tmp->data) {
2040 if (!last_read) {
2041 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002042 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002043 goto out;
2044 }
2045 break;
2046 }
2047 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2048 break;
2049
2050 case TCPCHK_EXPECT_CUSTOM:
2051 if (expect->custom)
2052 ret = expect->custom(check, rule, last_read);
2053 goto out;
2054 default:
2055 /* Should never happen. */
2056 ret = TCPCHK_EVAL_STOP;
2057 goto out;
2058 }
2059
2060
2061 /* Wait for more data on mismatch only if no minimum is defined (-1),
2062 * otherwise the absence of match is already conclusive.
2063 */
2064 if (!match && !last_read && (expect->min_recv == -1)) {
2065 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002066 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002067 goto out;
2068 }
2069
2070 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002071 if (match ^ inverse) {
2072 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002073 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002074 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002075
2076 error:
2077 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002078 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002079 ret = TCPCHK_EVAL_STOP;
2080 msg = alloc_trash_chunk();
2081 if (msg)
2082 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2083 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2084 free_trash_chunk(msg);
2085
2086 out:
2087 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002088 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002089 return ret;
2090}
2091
2092/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2093 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2094 * waits.
2095 */
2096enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2097{
2098 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2099 struct act_rule *act_rule;
2100 enum act_return act_ret;
2101
2102 act_rule =rule->action_kw.rule;
2103 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2104 if (act_ret != ACT_RET_CONT) {
2105 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2106 tcpcheck_get_step_id(check, rule));
2107 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2108 ret = TCPCHK_EVAL_STOP;
2109 }
2110
2111 return ret;
2112}
2113
2114/* Executes a tcp-check ruleset. Note that this is called both from the
2115 * connection's wake() callback and from the check scheduling task. It returns
2116 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2117 * presenting the risk of an fd replacement.
2118 *
2119 * Please do NOT place any return statement in this function and only leave
2120 * via the out_end_tcpcheck label after setting retcode.
2121 */
2122int tcpcheck_main(struct check *check)
2123{
2124 struct tcpcheck_rule *rule;
2125 struct conn_stream *cs = check->cs;
2126 struct connection *conn = cs_conn(cs);
2127 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002128 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002129 enum tcpcheck_eval_ret eval_ret;
2130
2131 /* here, we know that the check is complete or that it failed */
2132 if (check->result != CHK_RES_UNKNOWN)
2133 goto out;
2134
Christopher Faulet147b8c92021-04-10 09:00:38 +02002135 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2136
Willy Tarreau51cd5952020-06-05 12:25:38 +02002137 /* Note: the conn-stream and the connection may only be undefined before
2138 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002139 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002140 */
2141
2142 /* 1- check for connection error, if any */
2143 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2144 goto out_end_tcpcheck;
2145
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002146 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002147 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002148 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002149 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002150 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2151 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002152
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002153 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002154 * tcp-check variables */
2155 else {
2156 struct tcpcheck_var *var;
2157
2158 /* First evaluation, create a session */
2159 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2160 if (!check->sess) {
2161 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002162 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002163 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2164 goto out_end_tcpcheck;
2165 }
2166 vars_init(&check->vars, SCOPE_CHECK);
2167 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2168
2169 /* Preset tcp-check variables */
2170 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2171 struct sample smp;
2172
2173 memset(&smp, 0, sizeof(smp));
2174 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2175 smp.data = var->data;
2176 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2177 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002178 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002179 }
2180
2181 /* Now evaluate the tcp-check rules */
2182
2183 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2184 check->code = 0;
2185 switch (rule->action) {
2186 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002187 /* Not the first connection, release it first */
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002188 if (cs && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002189 check->state |= CHK_ST_CLOSE_CONN;
2190 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002191 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002192
2193 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002194
2195 /* We are still waiting the connection gets closed */
2196 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002197 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002198 eval_ret = TCPCHK_EVAL_WAIT;
2199 break;
2200 }
2201
Christopher Faulet147b8c92021-04-10 09:00:38 +02002202 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002203 eval_ret = tcpcheck_eval_connect(check, rule);
2204
2205 /* Refresh conn-stream and connection */
2206 cs = check->cs;
2207 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002208 last_read = 0;
2209 must_read = ((cs && IS_HTX_CS(cs)) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002210 break;
2211 case TCPCHK_ACT_SEND:
2212 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002213 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002214 eval_ret = tcpcheck_eval_send(check, rule);
2215 must_read = 1;
2216 break;
2217 case TCPCHK_ACT_EXPECT:
2218 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002219 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002220 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002221 eval_ret = tcpcheck_eval_recv(check, rule);
2222 if (eval_ret == TCPCHK_EVAL_STOP)
2223 goto out_end_tcpcheck;
2224 else if (eval_ret == TCPCHK_EVAL_WAIT)
2225 goto out;
2226 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2227 must_read = 0;
2228 }
2229
2230 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2231 ? tcpcheck_eval_expect_http(check, rule, last_read)
2232 : tcpcheck_eval_expect(check, rule, last_read));
2233
2234 if (eval_ret == TCPCHK_EVAL_WAIT) {
2235 check->current_step = rule->expect.head;
2236 if (!(check->wait_list.events & SUB_RETRY_RECV))
2237 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2238 }
2239 break;
2240 case TCPCHK_ACT_ACTION_KW:
2241 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002242 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002243 eval_ret = tcpcheck_eval_action_kw(check, rule);
2244 break;
2245 default:
2246 /* Otherwise, just go to the next one and don't update
2247 * the current step
2248 */
2249 eval_ret = TCPCHK_EVAL_CONTINUE;
2250 break;
2251 }
2252
2253 switch (eval_ret) {
2254 case TCPCHK_EVAL_CONTINUE:
2255 break;
2256 case TCPCHK_EVAL_WAIT:
2257 goto out;
2258 case TCPCHK_EVAL_STOP:
2259 goto out_end_tcpcheck;
2260 }
2261 }
2262
2263 /* All rules was evaluated */
2264 if (check->current_step) {
2265 rule = check->current_step;
2266
Christopher Faulet147b8c92021-04-10 09:00:38 +02002267 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2268
Willy Tarreau51cd5952020-06-05 12:25:38 +02002269 if (rule->action == TCPCHK_ACT_EXPECT) {
2270 struct buffer *msg;
2271 enum healthcheck_status status;
2272
2273 if (check->server &&
2274 (check->server->proxy->options & PR_O_DISABLE404) &&
2275 (check->server->next_state != SRV_ST_STOPPED) &&
2276 (check->code == 404)) {
2277 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002278 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002279 goto out_end_tcpcheck;
2280 }
2281
2282 msg = alloc_trash_chunk();
2283 if (msg)
2284 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2285 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2286 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2287 free_trash_chunk(msg);
2288 }
2289 else if (rule->action == TCPCHK_ACT_CONNECT) {
2290 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2291 enum healthcheck_status status = HCHK_STATUS_L4OK;
2292#ifdef USE_OPENSSL
2293 if (ssl_sock_is_ssl(conn))
2294 status = HCHK_STATUS_L6OK;
2295#endif
2296 set_server_check_status(check, status, msg);
2297 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002298 else
2299 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002300 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002301 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002302 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002303 }
2304 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002305
2306 out_end_tcpcheck:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002307 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) {
2308 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002309 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002310 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002311
Christopher Fauletb381a502020-11-25 13:47:00 +01002312 /* the tcpcheck is finished, release in/out buffer now */
2313 check_release_buf(check, &check->bi);
2314 check_release_buf(check, &check->bo);
2315
Willy Tarreau51cd5952020-06-05 12:25:38 +02002316 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002317 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002318 return retcode;
2319}
2320
2321
2322/**************************************************************************/
2323/******************* Internals to parse tcp-check rules *******************/
2324/**************************************************************************/
2325struct action_kw_list tcp_check_keywords = {
2326 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2327};
2328
2329/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2330 * returned on error.
2331 */
2332struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2333 struct list *rules, struct action_kw *kw,
2334 const char *file, int line, char **errmsg)
2335{
2336 struct tcpcheck_rule *chk = NULL;
2337 struct act_rule *actrule = NULL;
2338
2339 actrule = calloc(1, sizeof(*actrule));
2340 if (!actrule) {
2341 memprintf(errmsg, "out of memory");
2342 goto error;
2343 }
2344 actrule->kw = kw;
2345 actrule->from = ACT_F_TCP_CHK;
2346
2347 cur_arg++;
2348 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2349 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2350 goto error;
2351 }
2352
2353 chk = calloc(1, sizeof(*chk));
2354 if (!chk) {
2355 memprintf(errmsg, "out of memory");
2356 goto error;
2357 }
2358 chk->action = TCPCHK_ACT_ACTION_KW;
2359 chk->action_kw.rule = actrule;
2360 return chk;
2361
2362 error:
2363 free(actrule);
2364 return NULL;
2365}
2366
2367/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2368 * returned on error.
2369 */
2370struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2371 const char *file, int line, char **errmsg)
2372{
2373 struct tcpcheck_rule *chk = NULL;
2374 struct sockaddr_storage *sk = NULL;
2375 char *comment = NULL, *sni = NULL, *alpn = NULL;
2376 struct sample_expr *port_expr = NULL;
2377 const struct mux_proto_list *mux_proto = NULL;
2378 unsigned short conn_opts = 0;
2379 long port = 0;
2380 int alpn_len = 0;
2381
2382 list_for_each_entry(chk, rules, list) {
2383 if (chk->action == TCPCHK_ACT_CONNECT)
2384 break;
2385 if (chk->action == TCPCHK_ACT_COMMENT ||
2386 chk->action == TCPCHK_ACT_ACTION_KW ||
2387 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2388 continue;
2389
2390 memprintf(errmsg, "first step MUST also be a 'connect', "
2391 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2392 "when there is a 'connect' step in the tcp-check ruleset");
2393 goto error;
2394 }
2395
2396 cur_arg++;
2397 while (*(args[cur_arg])) {
2398 if (strcmp(args[cur_arg], "default") == 0)
2399 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2400 else if (strcmp(args[cur_arg], "addr") == 0) {
2401 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002402
2403 if (!*(args[cur_arg+1])) {
2404 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2405 goto error;
2406 }
2407
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002408 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2409 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002410 if (!sk) {
2411 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2412 goto error;
2413 }
2414
Willy Tarreau51cd5952020-06-05 12:25:38 +02002415 cur_arg++;
2416 }
2417 else if (strcmp(args[cur_arg], "port") == 0) {
2418 const char *p, *end;
2419
2420 if (!*(args[cur_arg+1])) {
2421 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2422 goto error;
2423 }
2424 cur_arg++;
2425
2426 port = 0;
2427 release_sample_expr(port_expr);
2428 p = args[cur_arg]; end = p + strlen(p);
2429 port = read_uint(&p, end);
2430 if (p != end) {
2431 int idx = 0;
2432
2433 px->conf.args.ctx = ARGC_SRV;
2434 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
2435 file, line, errmsg, &px->conf.args, NULL);
2436
2437 if (!port_expr) {
2438 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2439 goto error;
2440 }
2441 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2442 memprintf(errmsg, "error detected while parsing port expression : "
2443 " fetch method '%s' extracts information from '%s', "
2444 "none of which is available here.\n",
2445 args[cur_arg], sample_src_names(port_expr->fetch->use));
2446 goto error;
2447 }
2448 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2449 }
2450 else if (port > 65535 || port < 1) {
2451 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2452 args[cur_arg]);
2453 goto error;
2454 }
2455 }
2456 else if (strcmp(args[cur_arg], "proto") == 0) {
2457 if (!*(args[cur_arg+1])) {
2458 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2459 goto error;
2460 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002461 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002462 if (!mux_proto) {
2463 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2464 goto error;
2465 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002466
2467 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2468 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2469 goto error;
2470 }
2471 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2472 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2473 goto error;
2474 }
2475
Willy Tarreau51cd5952020-06-05 12:25:38 +02002476 cur_arg++;
2477 }
2478 else if (strcmp(args[cur_arg], "comment") == 0) {
2479 if (!*(args[cur_arg+1])) {
2480 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2481 goto error;
2482 }
2483 cur_arg++;
2484 free(comment);
2485 comment = strdup(args[cur_arg]);
2486 if (!comment) {
2487 memprintf(errmsg, "out of memory");
2488 goto error;
2489 }
2490 }
2491 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2492 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2493 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2494 conn_opts |= TCPCHK_OPT_SOCKS4;
2495 else if (strcmp(args[cur_arg], "linger") == 0)
2496 conn_opts |= TCPCHK_OPT_LINGER;
2497#ifdef USE_OPENSSL
2498 else if (strcmp(args[cur_arg], "ssl") == 0) {
2499 px->options |= PR_O_TCPCHK_SSL;
2500 conn_opts |= TCPCHK_OPT_SSL;
2501 }
2502 else if (strcmp(args[cur_arg], "sni") == 0) {
2503 if (!*(args[cur_arg+1])) {
2504 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2505 goto error;
2506 }
2507 cur_arg++;
2508 free(sni);
2509 sni = strdup(args[cur_arg]);
2510 if (!sni) {
2511 memprintf(errmsg, "out of memory");
2512 goto error;
2513 }
2514 }
2515 else if (strcmp(args[cur_arg], "alpn") == 0) {
2516#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2517 free(alpn);
2518 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2519 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2520 goto error;
2521 }
2522 cur_arg++;
2523#else
2524 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2525 goto error;
2526#endif
2527 }
2528#endif /* USE_OPENSSL */
2529
2530 else {
2531 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2532#ifdef USE_OPENSSL
2533 ", 'ssl', 'sni', 'alpn'"
2534#endif /* USE_OPENSSL */
2535 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2536 args[cur_arg]);
2537 goto error;
2538 }
2539 cur_arg++;
2540 }
2541
2542 chk = calloc(1, sizeof(*chk));
2543 if (!chk) {
2544 memprintf(errmsg, "out of memory");
2545 goto error;
2546 }
2547 chk->action = TCPCHK_ACT_CONNECT;
2548 chk->comment = comment;
2549 chk->connect.port = port;
2550 chk->connect.options = conn_opts;
2551 chk->connect.sni = sni;
2552 chk->connect.alpn = alpn;
2553 chk->connect.alpn_len= alpn_len;
2554 chk->connect.port_expr= port_expr;
2555 chk->connect.mux_proto= mux_proto;
2556 if (sk)
2557 chk->connect.addr = *sk;
2558 return chk;
2559
2560 error:
2561 free(alpn);
2562 free(sni);
2563 free(comment);
2564 release_sample_expr(port_expr);
2565 return NULL;
2566}
2567
2568/* Parses and creates a tcp-check send rule. NULL is returned on error */
2569struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2570 const char *file, int line, char **errmsg)
2571{
2572 struct tcpcheck_rule *chk = NULL;
2573 char *comment = NULL, *data = NULL;
2574 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2575
2576 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2577 type = TCPCHK_SEND_BINARY_LF;
2578 else if (strcmp(args[cur_arg], "send-binary") == 0)
2579 type = TCPCHK_SEND_BINARY;
2580 else if (strcmp(args[cur_arg], "send-lf") == 0)
2581 type = TCPCHK_SEND_STRING_LF;
2582 else if (strcmp(args[cur_arg], "send") == 0)
2583 type = TCPCHK_SEND_STRING;
2584
2585 if (!*(args[cur_arg+1])) {
2586 memprintf(errmsg, "'%s' expects a %s as argument",
2587 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2588 goto error;
2589 }
2590
2591 data = args[cur_arg+1];
2592
2593 cur_arg += 2;
2594 while (*(args[cur_arg])) {
2595 if (strcmp(args[cur_arg], "comment") == 0) {
2596 if (!*(args[cur_arg+1])) {
2597 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2598 goto error;
2599 }
2600 cur_arg++;
2601 free(comment);
2602 comment = strdup(args[cur_arg]);
2603 if (!comment) {
2604 memprintf(errmsg, "out of memory");
2605 goto error;
2606 }
2607 }
2608 else {
2609 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2610 args[cur_arg]);
2611 goto error;
2612 }
2613 cur_arg++;
2614 }
2615
2616 chk = calloc(1, sizeof(*chk));
2617 if (!chk) {
2618 memprintf(errmsg, "out of memory");
2619 goto error;
2620 }
2621 chk->action = TCPCHK_ACT_SEND;
2622 chk->comment = comment;
2623 chk->send.type = type;
2624
2625 switch (chk->send.type) {
2626 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002627 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002628 if (!isttest(chk->send.data)) {
2629 memprintf(errmsg, "out of memory");
2630 goto error;
2631 }
2632 break;
2633 case TCPCHK_SEND_BINARY: {
2634 int len = chk->send.data.len;
2635 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2636 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2637 goto error;
2638 }
2639 chk->send.data.len = len;
2640 break;
2641 }
2642 case TCPCHK_SEND_STRING_LF:
2643 case TCPCHK_SEND_BINARY_LF:
2644 LIST_INIT(&chk->send.fmt);
2645 px->conf.args.ctx = ARGC_SRV;
2646 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2647 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2648 goto error;
2649 }
2650 break;
2651 case TCPCHK_SEND_HTTP:
2652 case TCPCHK_SEND_UNDEF:
2653 goto error;
2654 }
2655
2656 return chk;
2657
2658 error:
2659 free(chk);
2660 free(comment);
2661 return NULL;
2662}
2663
2664/* Parses and creates a http-check send rule. NULL is returned on error */
2665struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2666 const char *file, int line, char **errmsg)
2667{
2668 struct tcpcheck_rule *chk = NULL;
2669 struct tcpcheck_http_hdr *hdr = NULL;
2670 struct http_hdr hdrs[global.tune.max_http_hdr];
2671 char *meth = NULL, *uri = NULL, *vsn = NULL;
2672 char *body = NULL, *comment = NULL;
2673 unsigned int flags = 0;
2674 int i = 0, host_hdr = -1;
2675
2676 cur_arg++;
2677 while (*(args[cur_arg])) {
2678 if (strcmp(args[cur_arg], "meth") == 0) {
2679 if (!*(args[cur_arg+1])) {
2680 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2681 goto error;
2682 }
2683 cur_arg++;
2684 meth = args[cur_arg];
2685 }
2686 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2687 if (!*(args[cur_arg+1])) {
2688 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2689 goto error;
2690 }
2691 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2692 if (strcmp(args[cur_arg], "uri-lf") == 0)
2693 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2694 cur_arg++;
2695 uri = args[cur_arg];
2696 }
2697 else if (strcmp(args[cur_arg], "ver") == 0) {
2698 if (!*(args[cur_arg+1])) {
2699 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2700 goto error;
2701 }
2702 cur_arg++;
2703 vsn = args[cur_arg];
2704 }
2705 else if (strcmp(args[cur_arg], "hdr") == 0) {
2706 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2707 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2708 goto error;
2709 }
2710
2711 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2712 if (host_hdr >= 0) {
2713 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2714 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2715 goto error;
2716 }
2717 host_hdr = i;
2718 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002719 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002720 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2721 goto skip_hdr;
2722
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002723 hdrs[i].n = ist(args[cur_arg + 1]);
2724 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002725 i++;
2726 skip_hdr:
2727 cur_arg += 2;
2728 }
2729 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2730 if (!*(args[cur_arg+1])) {
2731 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2732 goto error;
2733 }
2734 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2735 if (strcmp(args[cur_arg], "body-lf") == 0)
2736 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2737 cur_arg++;
2738 body = args[cur_arg];
2739 }
2740 else if (strcmp(args[cur_arg], "comment") == 0) {
2741 if (!*(args[cur_arg+1])) {
2742 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2743 goto error;
2744 }
2745 cur_arg++;
2746 free(comment);
2747 comment = strdup(args[cur_arg]);
2748 if (!comment) {
2749 memprintf(errmsg, "out of memory");
2750 goto error;
2751 }
2752 }
2753 else {
2754 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2755 " but got '%s' as argument.", args[cur_arg]);
2756 goto error;
2757 }
2758 cur_arg++;
2759 }
2760
2761 hdrs[i].n = hdrs[i].v = IST_NULL;
2762
2763 chk = calloc(1, sizeof(*chk));
2764 if (!chk) {
2765 memprintf(errmsg, "out of memory");
2766 goto error;
2767 }
2768 chk->action = TCPCHK_ACT_SEND;
2769 chk->comment = comment; comment = NULL;
2770 chk->send.type = TCPCHK_SEND_HTTP;
2771 chk->send.http.flags = flags;
2772 LIST_INIT(&chk->send.http.hdrs);
2773
2774 if (meth) {
2775 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2776 chk->send.http.meth.str.area = strdup(meth);
2777 chk->send.http.meth.str.data = strlen(meth);
2778 if (!chk->send.http.meth.str.area) {
2779 memprintf(errmsg, "out of memory");
2780 goto error;
2781 }
2782 }
2783 if (uri) {
2784 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2785 LIST_INIT(&chk->send.http.uri_fmt);
2786 px->conf.args.ctx = ARGC_SRV;
2787 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2788 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2789 goto error;
2790 }
2791 }
2792 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002793 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002794 if (!isttest(chk->send.http.uri)) {
2795 memprintf(errmsg, "out of memory");
2796 goto error;
2797 }
2798 }
2799 }
2800 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002801 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002802 if (!isttest(chk->send.http.vsn)) {
2803 memprintf(errmsg, "out of memory");
2804 goto error;
2805 }
2806 }
2807 for (i = 0; istlen(hdrs[i].n); i++) {
2808 hdr = calloc(1, sizeof(*hdr));
2809 if (!hdr) {
2810 memprintf(errmsg, "out of memory");
2811 goto error;
2812 }
2813 LIST_INIT(&hdr->value);
2814 hdr->name = istdup(hdrs[i].n);
2815 if (!isttest(hdr->name)) {
2816 memprintf(errmsg, "out of memory");
2817 goto error;
2818 }
2819
2820 ist0(hdrs[i].v);
2821 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2822 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002823 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002824 hdr = NULL;
2825 }
2826
2827 if (body) {
2828 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2829 LIST_INIT(&chk->send.http.body_fmt);
2830 px->conf.args.ctx = ARGC_SRV;
2831 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2832 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2833 goto error;
2834 }
2835 }
2836 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002837 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002838 if (!isttest(chk->send.http.body)) {
2839 memprintf(errmsg, "out of memory");
2840 goto error;
2841 }
2842 }
2843 }
2844
2845 return chk;
2846
2847 error:
2848 free_tcpcheck_http_hdr(hdr);
2849 free_tcpcheck(chk, 0);
2850 free(comment);
2851 return NULL;
2852}
2853
2854/* Parses and creates a http-check comment rule. NULL is returned on error */
2855struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2856 const char *file, int line, char **errmsg)
2857{
2858 struct tcpcheck_rule *chk = NULL;
2859 char *comment = NULL;
2860
2861 if (!*(args[cur_arg+1])) {
2862 memprintf(errmsg, "expects a string as argument");
2863 goto error;
2864 }
2865 cur_arg++;
2866 comment = strdup(args[cur_arg]);
2867 if (!comment) {
2868 memprintf(errmsg, "out of memory");
2869 goto error;
2870 }
2871
2872 chk = calloc(1, sizeof(*chk));
2873 if (!chk) {
2874 memprintf(errmsg, "out of memory");
2875 goto error;
2876 }
2877 chk->action = TCPCHK_ACT_COMMENT;
2878 chk->comment = comment;
2879 return chk;
2880
2881 error:
2882 free(comment);
2883 return NULL;
2884}
2885
2886/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2887 * on error. <proto> is set to the right protocol flags (covered by the
2888 * TCPCHK_RULES_PROTO_CHK mask).
2889 */
2890struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2891 struct list *rules, unsigned int proto,
2892 const char *file, int line, char **errmsg)
2893{
2894 struct tcpcheck_rule *prev_check, *chk = NULL;
2895 struct sample_expr *status_expr = NULL;
2896 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2897 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2898 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2899 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2900 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2901 unsigned int flags = 0;
2902 long min_recv = -1;
2903 int inverse = 0;
2904
2905 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2906 if (!*(args[cur_arg+1])) {
2907 memprintf(errmsg, "expects at least a matching pattern as arguments");
2908 goto error;
2909 }
2910
2911 cur_arg++;
2912 while (*(args[cur_arg])) {
2913 int in_pattern = 0;
2914
2915 rescan:
2916 if (strcmp(args[cur_arg], "min-recv") == 0) {
2917 if (in_pattern) {
2918 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2919 goto error;
2920 }
2921 if (!*(args[cur_arg+1])) {
2922 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2923 goto error;
2924 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002925 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002926 cur_arg++;
2927 min_recv = atol(args[cur_arg]);
2928 if (min_recv < -1 || min_recv > INT_MAX) {
2929 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2930 goto error;
2931 }
2932 }
2933 else if (*(args[cur_arg]) == '!') {
2934 in_pattern = 1;
2935 while (*(args[cur_arg]) == '!') {
2936 inverse = !inverse;
2937 args[cur_arg]++;
2938 }
2939 if (!*(args[cur_arg]))
2940 cur_arg++;
2941 goto rescan;
2942 }
2943 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2944 if (type != TCPCHK_EXPECT_UNDEF) {
2945 memprintf(errmsg, "only on pattern expected");
2946 goto error;
2947 }
2948 if (proto != TCPCHK_RULES_HTTP_CHK)
2949 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2950 else
2951 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2952
2953 if (!*(args[cur_arg+1])) {
2954 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2955 goto error;
2956 }
2957 cur_arg++;
2958 pattern = args[cur_arg];
2959 }
2960 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2961 if (proto == TCPCHK_RULES_HTTP_CHK)
2962 goto bad_http_kw;
2963 if (type != TCPCHK_EXPECT_UNDEF) {
2964 memprintf(errmsg, "only on pattern expected");
2965 goto error;
2966 }
2967 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2968
2969 if (!*(args[cur_arg+1])) {
2970 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2971 goto error;
2972 }
2973 cur_arg++;
2974 pattern = args[cur_arg];
2975 }
2976 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2977 if (type != TCPCHK_EXPECT_UNDEF) {
2978 memprintf(errmsg, "only on pattern expected");
2979 goto error;
2980 }
2981 if (proto != TCPCHK_RULES_HTTP_CHK)
2982 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2983 else {
2984 if (*(args[cur_arg]) != 's')
2985 goto bad_http_kw;
2986 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2987 }
2988
2989 if (!*(args[cur_arg+1])) {
2990 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2991 goto error;
2992 }
2993 cur_arg++;
2994 pattern = args[cur_arg];
2995 }
2996 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
2997 if (proto != TCPCHK_RULES_HTTP_CHK)
2998 goto bad_tcp_kw;
2999 if (type != TCPCHK_EXPECT_UNDEF) {
3000 memprintf(errmsg, "only on pattern expected");
3001 goto error;
3002 }
3003 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3004
3005 if (!*(args[cur_arg+1])) {
3006 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3007 goto error;
3008 }
3009 cur_arg++;
3010 pattern = args[cur_arg];
3011 }
3012 else if (strcmp(args[cur_arg], "custom") == 0) {
3013 if (in_pattern) {
3014 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3015 goto error;
3016 }
3017 if (type != TCPCHK_EXPECT_UNDEF) {
3018 memprintf(errmsg, "only on pattern expected");
3019 goto error;
3020 }
3021 type = TCPCHK_EXPECT_CUSTOM;
3022 }
3023 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3024 int orig_arg = cur_arg;
3025
3026 if (proto != TCPCHK_RULES_HTTP_CHK)
3027 goto bad_tcp_kw;
3028 if (type != TCPCHK_EXPECT_UNDEF) {
3029 memprintf(errmsg, "only on pattern expected");
3030 goto error;
3031 }
3032 type = TCPCHK_EXPECT_HTTP_HEADER;
3033
3034 if (strcmp(args[cur_arg], "fhdr") == 0)
3035 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3036
3037 /* Parse the name pattern, mandatory */
3038 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3039 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3040 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3041 args[orig_arg]);
3042 goto error;
3043 }
3044
3045 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3046 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3047
3048 cur_arg += 2;
3049 if (strcmp(args[cur_arg], "-m") == 0) {
3050 if (!*(args[cur_arg+1])) {
3051 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3052 args[orig_arg], args[cur_arg]);
3053 goto error;
3054 }
3055 if (strcmp(args[cur_arg+1], "str") == 0)
3056 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3057 else if (strcmp(args[cur_arg+1], "beg") == 0)
3058 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3059 else if (strcmp(args[cur_arg+1], "end") == 0)
3060 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3061 else if (strcmp(args[cur_arg+1], "sub") == 0)
3062 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3063 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3064 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3065 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3066 args[orig_arg]);
3067 goto error;
3068 }
3069 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3070 }
3071 else {
3072 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3073 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3074 goto error;
3075 }
3076 cur_arg += 2;
3077 }
3078 else
3079 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3080 npat = args[cur_arg];
3081
3082 if (!*(args[cur_arg+1]) ||
3083 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3084 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3085 goto next;
3086 }
3087 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3088 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3089
3090 /* Parse the value pattern, optional */
3091 if (strcmp(args[cur_arg+2], "-m") == 0) {
3092 cur_arg += 2;
3093 if (!*(args[cur_arg+1])) {
3094 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3095 args[orig_arg], args[cur_arg]);
3096 goto error;
3097 }
3098 if (strcmp(args[cur_arg+1], "str") == 0)
3099 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3100 else if (strcmp(args[cur_arg+1], "beg") == 0)
3101 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3102 else if (strcmp(args[cur_arg+1], "end") == 0)
3103 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3104 else if (strcmp(args[cur_arg+1], "sub") == 0)
3105 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3106 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3107 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3108 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3109 args[orig_arg]);
3110 goto error;
3111 }
3112 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3113 }
3114 else {
3115 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3116 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3117 goto error;
3118 }
3119 }
3120 else
3121 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3122
3123 if (!*(args[cur_arg+2])) {
3124 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3125 goto error;
3126 }
3127 vpat = args[cur_arg+2];
3128 cur_arg += 2;
3129 }
3130 else if (strcmp(args[cur_arg], "comment") == 0) {
3131 if (in_pattern) {
3132 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3133 goto error;
3134 }
3135 if (!*(args[cur_arg+1])) {
3136 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3137 goto error;
3138 }
3139 cur_arg++;
3140 free(comment);
3141 comment = strdup(args[cur_arg]);
3142 if (!comment) {
3143 memprintf(errmsg, "out of memory");
3144 goto error;
3145 }
3146 }
3147 else if (strcmp(args[cur_arg], "on-success") == 0) {
3148 if (in_pattern) {
3149 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3150 goto error;
3151 }
3152 if (!*(args[cur_arg+1])) {
3153 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3154 goto error;
3155 }
3156 cur_arg++;
3157 on_success_msg = args[cur_arg];
3158 }
3159 else if (strcmp(args[cur_arg], "on-error") == 0) {
3160 if (in_pattern) {
3161 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3162 goto error;
3163 }
3164 if (!*(args[cur_arg+1])) {
3165 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3166 goto error;
3167 }
3168 cur_arg++;
3169 on_error_msg = args[cur_arg];
3170 }
3171 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3172 if (in_pattern) {
3173 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3174 goto error;
3175 }
3176 if (!*(args[cur_arg+1])) {
3177 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3178 goto error;
3179 }
3180 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3181 ok_st = HCHK_STATUS_L7OKD;
3182 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3183 ok_st = HCHK_STATUS_L7OKCD;
3184 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3185 ok_st = HCHK_STATUS_L6OK;
3186 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3187 ok_st = HCHK_STATUS_L4OK;
3188 else {
3189 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3190 args[cur_arg], args[cur_arg+1]);
3191 goto error;
3192 }
3193 cur_arg++;
3194 }
3195 else if (strcmp(args[cur_arg], "error-status") == 0) {
3196 if (in_pattern) {
3197 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3198 goto error;
3199 }
3200 if (!*(args[cur_arg+1])) {
3201 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3202 goto error;
3203 }
3204 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3205 err_st = HCHK_STATUS_L7RSP;
3206 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3207 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003208 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3209 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003210 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3211 err_st = HCHK_STATUS_L6RSP;
3212 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3213 err_st = HCHK_STATUS_L4CON;
3214 else {
3215 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3216 args[cur_arg], args[cur_arg+1]);
3217 goto error;
3218 }
3219 cur_arg++;
3220 }
3221 else if (strcmp(args[cur_arg], "status-code") == 0) {
3222 int idx = 0;
3223
3224 if (in_pattern) {
3225 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3226 goto error;
3227 }
3228 if (!*(args[cur_arg+1])) {
3229 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3230 goto error;
3231 }
3232
3233 cur_arg++;
3234 release_sample_expr(status_expr);
3235 px->conf.args.ctx = ARGC_SRV;
3236 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3237 file, line, errmsg, &px->conf.args, NULL);
3238 if (!status_expr) {
3239 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3240 goto error;
3241 }
3242 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3243 memprintf(errmsg, "error detected while parsing status-code expression : "
3244 " fetch method '%s' extracts information from '%s', "
3245 "none of which is available here.\n",
3246 args[cur_arg], sample_src_names(status_expr->fetch->use));
3247 goto error;
3248 }
3249 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3250 }
3251 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3252 if (in_pattern) {
3253 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3254 goto error;
3255 }
3256 if (!*(args[cur_arg+1])) {
3257 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3258 goto error;
3259 }
3260 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3261 tout_st = HCHK_STATUS_L7TOUT;
3262 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3263 tout_st = HCHK_STATUS_L6TOUT;
3264 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3265 tout_st = HCHK_STATUS_L4TOUT;
3266 else {
3267 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3268 args[cur_arg], args[cur_arg+1]);
3269 goto error;
3270 }
3271 cur_arg++;
3272 }
3273 else {
3274 if (proto == TCPCHK_RULES_HTTP_CHK) {
3275 bad_http_kw:
3276 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3277 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3278 }
3279 else {
3280 bad_tcp_kw:
3281 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3282 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3283 }
3284 goto error;
3285 }
3286 next:
3287 cur_arg++;
3288 }
3289
3290 chk = calloc(1, sizeof(*chk));
3291 if (!chk) {
3292 memprintf(errmsg, "out of memory");
3293 goto error;
3294 }
3295 chk->action = TCPCHK_ACT_EXPECT;
3296 LIST_INIT(&chk->expect.onerror_fmt);
3297 LIST_INIT(&chk->expect.onsuccess_fmt);
3298 chk->comment = comment; comment = NULL;
3299 chk->expect.type = type;
3300 chk->expect.min_recv = min_recv;
3301 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3302 chk->expect.ok_status = ok_st;
3303 chk->expect.err_status = err_st;
3304 chk->expect.tout_status = tout_st;
3305 chk->expect.status_expr = status_expr; status_expr = NULL;
3306
3307 if (on_success_msg) {
3308 px->conf.args.ctx = ARGC_SRV;
3309 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3310 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3311 goto error;
3312 }
3313 }
3314 if (on_error_msg) {
3315 px->conf.args.ctx = ARGC_SRV;
3316 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3317 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3318 goto error;
3319 }
3320 }
3321
3322 switch (chk->expect.type) {
3323 case TCPCHK_EXPECT_HTTP_STATUS: {
3324 const char *p = pattern;
3325 unsigned int c1,c2;
3326
3327 chk->expect.codes.codes = NULL;
3328 chk->expect.codes.num = 0;
3329 while (1) {
3330 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3331 if (*p == '-') {
3332 p++;
3333 c2 = read_uint(&p, pattern + strlen(pattern));
3334 }
3335 if (c1 > c2) {
3336 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3337 goto error;
3338 }
3339
3340 chk->expect.codes.num++;
3341 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3342 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3343 if (!chk->expect.codes.codes) {
3344 memprintf(errmsg, "out of memory");
3345 goto error;
3346 }
3347 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3348 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3349
3350 if (*p == '\0')
3351 break;
3352 if (*p != ',') {
3353 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3354 goto error;
3355 }
3356 p++;
3357 }
3358 break;
3359 }
3360 case TCPCHK_EXPECT_STRING:
3361 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003362 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003363 if (!isttest(chk->expect.data)) {
3364 memprintf(errmsg, "out of memory");
3365 goto error;
3366 }
3367 break;
3368 case TCPCHK_EXPECT_BINARY: {
3369 int len = chk->expect.data.len;
3370
3371 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3372 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3373 goto error;
3374 }
3375 chk->expect.data.len = len;
3376 break;
3377 }
3378 case TCPCHK_EXPECT_STRING_REGEX:
3379 case TCPCHK_EXPECT_BINARY_REGEX:
3380 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3381 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3382 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3383 if (!chk->expect.regex)
3384 goto error;
3385 break;
3386
3387 case TCPCHK_EXPECT_STRING_LF:
3388 case TCPCHK_EXPECT_BINARY_LF:
3389 case TCPCHK_EXPECT_HTTP_BODY_LF:
3390 LIST_INIT(&chk->expect.fmt);
3391 px->conf.args.ctx = ARGC_SRV;
3392 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3393 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3394 goto error;
3395 }
3396 break;
3397
3398 case TCPCHK_EXPECT_HTTP_HEADER:
3399 if (!npat) {
3400 memprintf(errmsg, "unexpected error, undefined header name pattern");
3401 goto error;
3402 }
3403 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3404 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3405 if (!chk->expect.hdr.name_re)
3406 goto error;
3407 }
3408 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3409 px->conf.args.ctx = ARGC_SRV;
3410 LIST_INIT(&chk->expect.hdr.name_fmt);
3411 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3412 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3413 goto error;
3414 }
3415 }
3416 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003417 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003418 if (!isttest(chk->expect.hdr.name)) {
3419 memprintf(errmsg, "out of memory");
3420 goto error;
3421 }
3422 }
3423
3424 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3425 chk->expect.hdr.value = IST_NULL;
3426 break;
3427 }
3428
3429 if (!vpat) {
3430 memprintf(errmsg, "unexpected error, undefined header value pattern");
3431 goto error;
3432 }
3433 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3434 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3435 if (!chk->expect.hdr.value_re)
3436 goto error;
3437 }
3438 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3439 px->conf.args.ctx = ARGC_SRV;
3440 LIST_INIT(&chk->expect.hdr.value_fmt);
3441 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3442 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3443 goto error;
3444 }
3445 }
3446 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003447 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003448 if (!isttest(chk->expect.hdr.value)) {
3449 memprintf(errmsg, "out of memory");
3450 goto error;
3451 }
3452 }
3453
3454 break;
3455 case TCPCHK_EXPECT_CUSTOM:
3456 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3457 break;
3458 case TCPCHK_EXPECT_UNDEF:
3459 memprintf(errmsg, "pattern not found");
3460 goto error;
3461 }
3462
3463 /* All tcp-check expect points back to the first inverse expect rule in
3464 * a chain of one or more expect rule, potentially itself.
3465 */
3466 chk->expect.head = chk;
3467 list_for_each_entry_rev(prev_check, rules, list) {
3468 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3469 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3470 chk->expect.head = prev_check;
3471 continue;
3472 }
3473 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3474 break;
3475 }
3476 return chk;
3477
3478 error:
3479 free_tcpcheck(chk, 0);
3480 free(comment);
3481 release_sample_expr(status_expr);
3482 return NULL;
3483}
3484
3485/* Overwrites fields of the old http send rule with those of the new one. When
3486 * replaced, old values are freed and replaced by the new ones. New values are
3487 * not copied but transferred. At the end <new> should be empty and can be
3488 * safely released. This function never fails.
3489 */
3490void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3491{
3492 struct logformat_node *lf, *lfb;
3493 struct tcpcheck_http_hdr *hdr, *bhdr;
3494
3495
3496 if (new->send.http.meth.str.area) {
3497 free(old->send.http.meth.str.area);
3498 old->send.http.meth.meth = new->send.http.meth.meth;
3499 old->send.http.meth.str.area = new->send.http.meth.str.area;
3500 old->send.http.meth.str.data = new->send.http.meth.str.data;
3501 new->send.http.meth.str = BUF_NULL;
3502 }
3503
3504 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3505 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3506 istfree(&old->send.http.uri);
3507 else
3508 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3509 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3510 old->send.http.uri = new->send.http.uri;
3511 new->send.http.uri = IST_NULL;
3512 }
3513 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3514 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3515 istfree(&old->send.http.uri);
3516 else
3517 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3518 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3519 LIST_INIT(&old->send.http.uri_fmt);
3520 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003521 LIST_DELETE(&lf->list);
3522 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003523 }
3524 }
3525
3526 if (isttest(new->send.http.vsn)) {
3527 istfree(&old->send.http.vsn);
3528 old->send.http.vsn = new->send.http.vsn;
3529 new->send.http.vsn = IST_NULL;
3530 }
3531
3532 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3533 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003534 LIST_DELETE(&hdr->list);
3535 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003536 }
3537
3538 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3539 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3540 istfree(&old->send.http.body);
3541 else
3542 free_tcpcheck_fmt(&old->send.http.body_fmt);
3543 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3544 old->send.http.body = new->send.http.body;
3545 new->send.http.body = IST_NULL;
3546 }
3547 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3548 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3549 istfree(&old->send.http.body);
3550 else
3551 free_tcpcheck_fmt(&old->send.http.body_fmt);
3552 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3553 LIST_INIT(&old->send.http.body_fmt);
3554 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003555 LIST_DELETE(&lf->list);
3556 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003557 }
3558 }
3559}
3560
3561/* Internal function used to add an http-check rule in a list during the config
3562 * parsing step. Depending on its type, and the previously inserted rules, a
3563 * specific action may be performed or an error may be reported. This functions
3564 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3565 * message.
3566 */
3567int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3568{
3569 struct tcpcheck_rule *r;
3570
3571 /* the implicit send rule coming from an "option httpchk" line must be
3572 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003573 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003574 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003575 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003576 * sure the ruleset remains valid.
3577 */
3578
3579 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3580 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3581 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3582 * following tests are performed :
3583 *
3584 * 1- If there is no such rule or if it is not a send rule, the implicit send
3585 * rule is pushed in front of the ruleset
3586 *
3587 * 2- If it is another implicit send rule, it is replaced with the new one.
3588 *
3589 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3590 * both, overwriting the old send rule (the explicit one) with info of the
3591 * new send rule (the implicit one).
3592 */
3593 r = get_first_tcpcheck_rule(rules);
3594 if (r && r->action == TCPCHK_ACT_CONNECT)
3595 r = get_next_tcpcheck_rule(rules, r);
3596 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003597 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003598 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003599 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003600 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003601 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003602 }
3603 else {
3604 tcpcheck_overwrite_send_http_rule(r, chk);
3605 free_tcpcheck(chk, 0);
3606 }
3607 }
3608 else {
3609 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3610 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3611 * with an existing implicit send rule, if any. At the end, if there is no error,
3612 * the rule is appended to the list.
3613 */
3614
3615 r = get_last_tcpcheck_rule(rules);
3616 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3617 /* no error */;
3618 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3619 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3620 chk->index+1);
3621 return 0;
3622 }
3623 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3624 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3625 chk->index+1);
3626 return 0;
3627 }
3628 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3629 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3630 chk->index+1);
3631 return 0;
3632 }
3633
3634 if (chk->action == TCPCHK_ACT_SEND) {
3635 r = get_first_tcpcheck_rule(rules);
3636 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3637 tcpcheck_overwrite_send_http_rule(r, chk);
3638 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003639 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003640 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3641 chk = r;
3642 }
3643 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003644 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003645 }
3646 return 1;
3647}
3648
3649/* Check tcp-check health-check configuration for the proxy <px>. */
3650static int check_proxy_tcpcheck(struct proxy *px)
3651{
3652 struct tcpcheck_rule *chk, *back;
3653 char *comment = NULL, *errmsg = NULL;
3654 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003655 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003656
3657 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3658 deinit_proxy_tcpcheck(px);
3659 goto out;
3660 }
3661
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003662 ha_free(&px->check_command);
3663 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003664
3665 if (!px->tcpcheck_rules.list) {
3666 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
3667 ret |= ERR_ALERT | ERR_FATAL;
3668 goto out;
3669 }
3670
3671 /* HTTP ruleset only : */
3672 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3673 struct tcpcheck_rule *next;
3674
3675 /* move remaining implicit send rule from "option httpchk" line to the right place.
3676 * If such rule exists, it must be the first one. In this case, the rule is moved
3677 * after the first connect rule, if any. Otherwise, nothing is done.
3678 */
3679 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3680 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3681 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3682 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003683 LIST_DELETE(&chk->list);
3684 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003685 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003686 }
3687 }
3688
3689 /* add implicit expect rule if the last one is a send. It is inherited from previous
3690 * versions where the http expect rule was optional. Now it is possible to chained
3691 * send/expect rules but the last expect may still be implicit.
3692 */
3693 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3694 if (chk && chk->action == TCPCHK_ACT_SEND) {
3695 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3696 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3697 px->conf.file, px->conf.line, &errmsg);
3698 if (!next) {
3699 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
3700 "(%s).\n", px->id, errmsg);
3701 free(errmsg);
3702 ret |= ERR_ALERT | ERR_FATAL;
3703 goto out;
3704 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003705 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003706 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003707 }
3708 }
3709
3710 /* For all ruleset: */
3711
3712 /* If there is no connect rule preceding all send / expect rules, an
3713 * implicit one is inserted before all others.
3714 */
3715 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3716 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3717 chk = calloc(1, sizeof(*chk));
3718 if (!chk) {
3719 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
3720 "(out of memory).\n", px->id);
3721 ret |= ERR_ALERT | ERR_FATAL;
3722 goto out;
3723 }
3724 chk->action = TCPCHK_ACT_CONNECT;
3725 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003726 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003727 }
3728
3729 /* Remove all comment rules. To do so, when a such rule is found, the
3730 * comment is assigned to the following rule(s).
3731 */
3732 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003733 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3734 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003735
3736 prev_action = chk->action;
3737 switch (chk->action) {
3738 case TCPCHK_ACT_COMMENT:
3739 free(comment);
3740 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003741 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003742 free(chk);
3743 break;
3744 case TCPCHK_ACT_CONNECT:
3745 if (!chk->comment && comment)
3746 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003747 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003748 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003749 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003750 break;
3751 case TCPCHK_ACT_SEND:
3752 case TCPCHK_ACT_EXPECT:
3753 if (!chk->comment && comment)
3754 chk->comment = strdup(comment);
3755 break;
3756 }
3757 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003758 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003759
3760 out:
3761 return ret;
3762}
3763
3764void deinit_proxy_tcpcheck(struct proxy *px)
3765{
3766 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3767 px->tcpcheck_rules.flags = 0;
3768 px->tcpcheck_rules.list = NULL;
3769}
3770
3771static void deinit_tcpchecks()
3772{
3773 struct tcpcheck_ruleset *rs;
3774 struct tcpcheck_rule *r, *rb;
3775 struct ebpt_node *node, *next;
3776
3777 node = ebpt_first(&shared_tcpchecks);
3778 while (node) {
3779 next = ebpt_next(node);
3780 ebpt_delete(node);
3781 free(node->key);
3782 rs = container_of(node, typeof(*rs), node);
3783 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003784 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003785 free_tcpcheck(r, 0);
3786 }
3787 free(rs);
3788 node = next;
3789 }
3790}
3791
3792int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3793{
3794 struct tcpcheck_rule *tcpcheck, *prev_check;
3795 struct tcpcheck_expect *expect;
3796
Willy Tarreau6922e552021-03-22 21:11:45 +01003797 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003798 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003799 tcpcheck->action = TCPCHK_ACT_EXPECT;
3800
3801 expect = &tcpcheck->expect;
3802 expect->type = TCPCHK_EXPECT_STRING;
3803 LIST_INIT(&expect->onerror_fmt);
3804 LIST_INIT(&expect->onsuccess_fmt);
3805 expect->ok_status = HCHK_STATUS_L7OKD;
3806 expect->err_status = HCHK_STATUS_L7RSP;
3807 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003808 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003809 if (!isttest(expect->data)) {
3810 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3811 return 0;
3812 }
3813
3814 /* All tcp-check expect points back to the first inverse expect rule
3815 * in a chain of one or more expect rule, potentially itself.
3816 */
3817 tcpcheck->expect.head = tcpcheck;
3818 list_for_each_entry_rev(prev_check, rules->list, list) {
3819 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3820 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3821 tcpcheck->expect.head = prev_check;
3822 continue;
3823 }
3824 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3825 break;
3826 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003827 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003828 return 1;
3829}
3830
3831int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3832{
3833 struct tcpcheck_rule *tcpcheck;
3834 struct tcpcheck_send *send;
3835 const char *in;
3836 char *dst;
3837 int i;
3838
Willy Tarreau6922e552021-03-22 21:11:45 +01003839 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003840 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003841 tcpcheck->action = TCPCHK_ACT_SEND;
3842
3843 send = &tcpcheck->send;
3844 send->type = TCPCHK_SEND_STRING;
3845
3846 for (i = 0; strs[i]; i++)
3847 send->data.len += strlen(strs[i]);
3848
3849 send->data.ptr = malloc(istlen(send->data) + 1);
3850 if (!isttest(send->data)) {
3851 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3852 return 0;
3853 }
3854
3855 dst = istptr(send->data);
3856 for (i = 0; strs[i]; i++)
3857 for (in = strs[i]; (*dst = *in++); dst++);
3858 *dst = 0;
3859
Willy Tarreau2b718102021-04-21 07:32:39 +02003860 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003861 return 1;
3862}
3863
3864/* Parses the "tcp-check" proxy keyword */
3865static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003866 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003867 char **errmsg)
3868{
3869 struct tcpcheck_ruleset *rs = NULL;
3870 struct tcpcheck_rule *chk = NULL;
3871 int index, cur_arg, ret = 0;
3872
3873 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3874 ret = 1;
3875
3876 /* Deduce the ruleset name from the proxy info */
3877 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3878 ((curpx == defpx) ? "defaults" : curpx->id),
3879 curpx->conf.file, curpx->conf.line);
3880
3881 rs = find_tcpcheck_ruleset(b_orig(&trash));
3882 if (rs == NULL) {
3883 rs = create_tcpcheck_ruleset(b_orig(&trash));
3884 if (rs == NULL) {
3885 memprintf(errmsg, "out of memory.\n");
3886 goto error;
3887 }
3888 }
3889
3890 index = 0;
3891 if (!LIST_ISEMPTY(&rs->rules)) {
3892 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3893 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003894 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003895 }
3896
3897 cur_arg = 1;
3898 if (strcmp(args[cur_arg], "connect") == 0)
3899 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3900 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3901 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3902 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3903 else if (strcmp(args[cur_arg], "expect") == 0)
3904 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3905 else if (strcmp(args[cur_arg], "comment") == 0)
3906 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3907 else {
3908 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3909
3910 if (!kw) {
3911 action_kw_tcp_check_build_list(&trash);
3912 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3913 "%s%s. but got '%s'",
3914 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3915 goto error;
3916 }
3917 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3918 }
3919
3920 if (!chk) {
3921 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3922 goto error;
3923 }
3924 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3925
3926 /* No error: add the tcp-check rule in the list */
3927 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003928 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003929
3930 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3931 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3932 /* Use this ruleset if the proxy already has tcp-check enabled */
3933 curpx->tcpcheck_rules.list = &rs->rules;
3934 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3935 }
3936 else {
3937 /* mark this ruleset as unused for now */
3938 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3939 }
3940
3941 return ret;
3942
3943 error:
3944 free_tcpcheck(chk, 0);
3945 free_tcpcheck_ruleset(rs);
3946 return -1;
3947}
3948
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003949/* Parses the "http-check" proxy keyword */
3950static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003951 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003952 char **errmsg)
3953{
3954 struct tcpcheck_ruleset *rs = NULL;
3955 struct tcpcheck_rule *chk = NULL;
3956 int index, cur_arg, ret = 0;
3957
3958 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3959 ret = 1;
3960
3961 cur_arg = 1;
3962 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3963 /* enable a graceful server shutdown on an HTTP 404 response */
3964 curpx->options |= PR_O_DISABLE404;
3965 if (too_many_args(1, args, errmsg, NULL))
3966 goto error;
3967 goto out;
3968 }
3969 else if (strcmp(args[cur_arg], "send-state") == 0) {
3970 /* enable emission of the apparent state of a server in HTTP checks */
3971 curpx->options2 |= PR_O2_CHK_SNDST;
3972 if (too_many_args(1, args, errmsg, NULL))
3973 goto error;
3974 goto out;
3975 }
3976
3977 /* Deduce the ruleset name from the proxy info */
3978 chunk_printf(&trash, "*http-check-%s_%s-%d",
3979 ((curpx == defpx) ? "defaults" : curpx->id),
3980 curpx->conf.file, curpx->conf.line);
3981
3982 rs = find_tcpcheck_ruleset(b_orig(&trash));
3983 if (rs == NULL) {
3984 rs = create_tcpcheck_ruleset(b_orig(&trash));
3985 if (rs == NULL) {
3986 memprintf(errmsg, "out of memory.\n");
3987 goto error;
3988 }
3989 }
3990
3991 index = 0;
3992 if (!LIST_ISEMPTY(&rs->rules)) {
3993 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3994 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
3995 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003996 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003997 }
3998
3999 if (strcmp(args[cur_arg], "connect") == 0)
4000 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4001 else if (strcmp(args[cur_arg], "send") == 0)
4002 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4003 else if (strcmp(args[cur_arg], "expect") == 0)
4004 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4005 file, line, errmsg);
4006 else if (strcmp(args[cur_arg], "comment") == 0)
4007 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4008 else {
4009 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4010
4011 if (!kw) {
4012 action_kw_tcp_check_build_list(&trash);
4013 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4014 " 'send', 'expect'%s%s. but got '%s'",
4015 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4016 goto error;
4017 }
4018 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4019 }
4020
4021 if (!chk) {
4022 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4023 goto error;
4024 }
4025 ret = (*errmsg != NULL); /* Handle warning */
4026
4027 chk->index = index;
4028 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4029 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4030 /* Use this ruleset if the proxy already has http-check enabled */
4031 curpx->tcpcheck_rules.list = &rs->rules;
4032 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4033 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4034 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4035 curpx->tcpcheck_rules.list = NULL;
4036 goto error;
4037 }
4038 }
4039 else {
4040 /* mark this ruleset as unused for now */
4041 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004042 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004043 }
4044
4045 out:
4046 return ret;
4047
4048 error:
4049 free_tcpcheck(chk, 0);
4050 free_tcpcheck_ruleset(rs);
4051 return -1;
4052}
4053
4054/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004055int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004056 const char *file, int line)
4057{
4058 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4059 static char *redis_res = "+PONG\r\n";
4060
4061 struct tcpcheck_ruleset *rs = NULL;
4062 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4063 struct tcpcheck_rule *chk;
4064 char *errmsg = NULL;
4065 int err_code = 0;
4066
4067 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4068 err_code |= ERR_WARN;
4069
4070 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4071 goto out;
4072
4073 curpx->options2 &= ~PR_O2_CHK_ANY;
4074 curpx->options2 |= PR_O2_TCPCHK_CHK;
4075
4076 free_tcpcheck_vars(&rules->preset_vars);
4077 rules->list = NULL;
4078 rules->flags = 0;
4079
4080 rs = find_tcpcheck_ruleset("*redis-check");
4081 if (rs)
4082 goto ruleset_found;
4083
4084 rs = create_tcpcheck_ruleset("*redis-check");
4085 if (rs == NULL) {
4086 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4087 goto error;
4088 }
4089
4090 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4091 1, curpx, &rs->rules, file, line, &errmsg);
4092 if (!chk) {
4093 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4094 goto error;
4095 }
4096 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004097 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004098
4099 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4100 "error-status", "L7STS",
4101 "on-error", "%[res.payload(0,0),cut_crlf]",
4102 "on-success", "Redis server is ok",
4103 ""},
4104 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4105 if (!chk) {
4106 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4107 goto error;
4108 }
4109 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004110 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004111
4112 ruleset_found:
4113 rules->list = &rs->rules;
4114 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4115 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4116
4117 out:
4118 free(errmsg);
4119 return err_code;
4120
4121 error:
4122 free_tcpcheck_ruleset(rs);
4123 err_code |= ERR_ALERT | ERR_FATAL;
4124 goto out;
4125}
4126
4127
4128/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004129int 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 +01004130 const char *file, int line)
4131{
4132 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4133 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4134 *
4135 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4136 */
4137 static char sslv3_client_hello[] = {
4138 "16" /* ContentType : 0x16 = Handshake */
4139 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4140 "0079" /* ContentLength : 0x79 bytes after this one */
4141 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4142 "000075" /* HandshakeLength : 0x75 bytes after this one */
4143 "0300" /* Hello Version : 0x0300 = v3 */
4144 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4145 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4146 "00" /* Session ID length : empty (no session ID) */
4147 "004E" /* Cipher Suite Length : 78 bytes after this one */
4148 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4149 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4150 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4151 "000D" "000E" "000F" "0010" /* various bit lengths, */
4152 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4153 "0015" "0016" "0017" "0018"
4154 "0019" "001A" "001B" "002F"
4155 "0030" "0031" "0032" "0033"
4156 "0034" "0035" "0036" "0037"
4157 "0038" "0039" "003A"
4158 "01" /* Compression Length : 0x01 = 1 byte for types */
4159 "00" /* Compression Type : 0x00 = NULL compression */
4160 };
4161
4162 struct tcpcheck_ruleset *rs = NULL;
4163 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4164 struct tcpcheck_rule *chk;
4165 char *errmsg = NULL;
4166 int err_code = 0;
4167
4168 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4169 err_code |= ERR_WARN;
4170
4171 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4172 goto out;
4173
4174 curpx->options2 &= ~PR_O2_CHK_ANY;
4175 curpx->options2 |= PR_O2_TCPCHK_CHK;
4176
4177 free_tcpcheck_vars(&rules->preset_vars);
4178 rules->list = NULL;
4179 rules->flags = 0;
4180
4181 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4182 if (rs)
4183 goto ruleset_found;
4184
4185 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4186 if (rs == NULL) {
4187 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4188 goto error;
4189 }
4190
4191 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4192 1, curpx, &rs->rules, file, line, &errmsg);
4193 if (!chk) {
4194 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4195 goto error;
4196 }
4197 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004198 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004199
4200 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4201 "min-recv", "5", "ok-status", "L6OK",
4202 "error-status", "L6RSP", "tout-status", "L6TOUT",
4203 ""},
4204 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4205 if (!chk) {
4206 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4207 goto error;
4208 }
4209 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004210 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004211
4212 ruleset_found:
4213 rules->list = &rs->rules;
4214 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4215 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4216
4217 out:
4218 free(errmsg);
4219 return err_code;
4220
4221 error:
4222 free_tcpcheck_ruleset(rs);
4223 err_code |= ERR_ALERT | ERR_FATAL;
4224 goto out;
4225}
4226
4227/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004228int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004229 const char *file, int line)
4230{
4231 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4232
4233 struct tcpcheck_ruleset *rs = NULL;
4234 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4235 struct tcpcheck_rule *chk;
4236 struct tcpcheck_var *var = NULL;
4237 char *cmd = NULL, *errmsg = NULL;
4238 int err_code = 0;
4239
4240 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4241 err_code |= ERR_WARN;
4242
4243 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4244 goto out;
4245
4246 curpx->options2 &= ~PR_O2_CHK_ANY;
4247 curpx->options2 |= PR_O2_TCPCHK_CHK;
4248
4249 free_tcpcheck_vars(&rules->preset_vars);
4250 rules->list = NULL;
4251 rules->flags = 0;
4252
4253 cur_arg += 2;
4254 if (*args[cur_arg] && *args[cur_arg+1] &&
4255 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4256 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4257 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4258 if (cmd)
4259 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4260 }
4261 else {
4262 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4263 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4264 cmd = strdup("HELO localhost");
4265 }
4266
4267 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4268 if (cmd == NULL || var == NULL) {
4269 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4270 goto error;
4271 }
4272 var->data.type = SMP_T_STR;
4273 var->data.u.str.area = cmd;
4274 var->data.u.str.data = strlen(cmd);
4275 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004276 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004277 cmd = NULL;
4278 var = NULL;
4279
4280 rs = find_tcpcheck_ruleset("*smtp-check");
4281 if (rs)
4282 goto ruleset_found;
4283
4284 rs = create_tcpcheck_ruleset("*smtp-check");
4285 if (rs == NULL) {
4286 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4287 goto error;
4288 }
4289
4290 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4291 1, curpx, &rs->rules, file, line, &errmsg);
4292 if (!chk) {
4293 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4294 goto error;
4295 }
4296 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004297 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004298
4299 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4300 "min-recv", "4",
4301 "error-status", "L7RSP",
4302 "on-error", "%[res.payload(0,0),cut_crlf]",
4303 ""},
4304 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4305 if (!chk) {
4306 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4307 goto error;
4308 }
4309 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004310 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004311
4312 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4313 "min-recv", "4",
4314 "error-status", "L7STS",
4315 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4316 "status-code", "res.payload(0,3)",
4317 ""},
4318 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4319 if (!chk) {
4320 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4321 goto error;
4322 }
4323 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004324 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004325
4326 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4327 1, curpx, &rs->rules, file, line, &errmsg);
4328 if (!chk) {
4329 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4330 goto error;
4331 }
4332 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004333 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004334
4335 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4336 "min-recv", "4",
4337 "error-status", "L7STS",
4338 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4339 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4340 "status-code", "res.payload(0,3)",
4341 ""},
4342 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4343 if (!chk) {
4344 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4345 goto error;
4346 }
4347 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004348 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004349
4350 ruleset_found:
4351 rules->list = &rs->rules;
4352 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4353 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4354
4355 out:
4356 free(errmsg);
4357 return err_code;
4358
4359 error:
4360 free(cmd);
4361 free(var);
4362 free_tcpcheck_vars(&rules->preset_vars);
4363 free_tcpcheck_ruleset(rs);
4364 err_code |= ERR_ALERT | ERR_FATAL;
4365 goto out;
4366}
4367
4368/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004369int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004370 const char *file, int line)
4371{
4372 static char pgsql_req[] = {
4373 "%[var(check.plen),htonl,hex]" /* The packet length*/
4374 "00030000" /* the version 3.0 */
4375 "7573657200" /* "user" key */
4376 "%[var(check.username),hex]00" /* the username */
4377 "00"
4378 };
4379
4380 struct tcpcheck_ruleset *rs = NULL;
4381 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4382 struct tcpcheck_rule *chk;
4383 struct tcpcheck_var *var = NULL;
4384 char *user = NULL, *errmsg = NULL;
4385 size_t packetlen = 0;
4386 int err_code = 0;
4387
4388 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4389 err_code |= ERR_WARN;
4390
4391 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4392 goto out;
4393
4394 curpx->options2 &= ~PR_O2_CHK_ANY;
4395 curpx->options2 |= PR_O2_TCPCHK_CHK;
4396
4397 free_tcpcheck_vars(&rules->preset_vars);
4398 rules->list = NULL;
4399 rules->flags = 0;
4400
4401 cur_arg += 2;
4402 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4403 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4404 file, line, args[0], args[1]);
4405 goto error;
4406 }
4407 if (strcmp(args[cur_arg], "user") == 0) {
4408 packetlen = 15 + strlen(args[cur_arg+1]);
4409 user = strdup(args[cur_arg+1]);
4410
4411 var = create_tcpcheck_var(ist("check.username"));
4412 if (user == NULL || var == NULL) {
4413 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4414 goto error;
4415 }
4416 var->data.type = SMP_T_STR;
4417 var->data.u.str.area = user;
4418 var->data.u.str.data = strlen(user);
4419 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004420 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004421 user = NULL;
4422 var = NULL;
4423
4424 var = create_tcpcheck_var(ist("check.plen"));
4425 if (var == NULL) {
4426 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4427 goto error;
4428 }
4429 var->data.type = SMP_T_SINT;
4430 var->data.u.sint = packetlen;
4431 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004432 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004433 var = NULL;
4434 }
4435 else {
4436 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4437 file, line, args[0], args[1]);
4438 goto error;
4439 }
4440
4441 rs = find_tcpcheck_ruleset("*pgsql-check");
4442 if (rs)
4443 goto ruleset_found;
4444
4445 rs = create_tcpcheck_ruleset("*pgsql-check");
4446 if (rs == NULL) {
4447 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4448 goto error;
4449 }
4450
4451 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4452 1, curpx, &rs->rules, file, line, &errmsg);
4453 if (!chk) {
4454 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4455 goto error;
4456 }
4457 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004458 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004459
4460 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4461 1, curpx, &rs->rules, file, line, &errmsg);
4462 if (!chk) {
4463 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4464 goto error;
4465 }
4466 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004467 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004468
4469 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4470 "min-recv", "5",
4471 "error-status", "L7RSP",
4472 "on-error", "%[res.payload(6,0)]",
4473 ""},
4474 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4475 if (!chk) {
4476 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4477 goto error;
4478 }
4479 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004480 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004481
4482 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4483 "min-recv", "9",
4484 "error-status", "L7STS",
4485 "on-success", "PostgreSQL server is ok",
4486 "on-error", "PostgreSQL unknown error",
4487 ""},
4488 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4489 if (!chk) {
4490 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4491 goto error;
4492 }
4493 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004494 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004495
4496 ruleset_found:
4497 rules->list = &rs->rules;
4498 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4499 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4500
4501 out:
4502 free(errmsg);
4503 return err_code;
4504
4505 error:
4506 free(user);
4507 free(var);
4508 free_tcpcheck_vars(&rules->preset_vars);
4509 free_tcpcheck_ruleset(rs);
4510 err_code |= ERR_ALERT | ERR_FATAL;
4511 goto out;
4512}
4513
4514
4515/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004516int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004517 const char *file, int line)
4518{
4519 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4520 * const char mysql40_client_auth_pkt[] = {
4521 * "\x0e\x00\x00" // packet length
4522 * "\x01" // packet number
4523 * "\x00\x00" // client capabilities
4524 * "\x00\x00\x01" // max packet
4525 * "haproxy\x00" // username (null terminated string)
4526 * "\x00" // filler (always 0x00)
4527 * "\x01\x00\x00" // packet length
4528 * "\x00" // packet number
4529 * "\x01" // COM_QUIT command
4530 * };
4531 */
4532 static char mysql40_rsname[] = "*mysql40-check";
4533 static char mysql40_req[] = {
4534 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4535 "0080" /* client capabilities */
4536 "000001" /* max packet */
4537 "%[var(check.username),hex]00" /* the username */
4538 "00" /* filler (always 0x00) */
4539 "010000" /* packet length*/
4540 "00" /* sequence ID */
4541 "01" /* COM_QUIT command */
4542 };
4543
4544 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4545 * const char mysql41_client_auth_pkt[] = {
4546 * "\x0e\x00\x00\" // packet length
4547 * "\x01" // packet number
4548 * "\x00\x00\x00\x00" // client capabilities
4549 * "\x00\x00\x00\x01" // max packet
4550 * "\x21" // character set (UTF-8)
4551 * char[23] // All zeroes
4552 * "haproxy\x00" // username (null terminated string)
4553 * "\x00" // filler (always 0x00)
4554 * "\x01\x00\x00" // packet length
4555 * "\x00" // packet number
4556 * "\x01" // COM_QUIT command
4557 * };
4558 */
4559 static char mysql41_rsname[] = "*mysql41-check";
4560 static char mysql41_req[] = {
4561 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4562 "00820000" /* client capabilities */
4563 "00800001" /* max packet */
4564 "21" /* character set (UTF-8) */
4565 "000000000000000000000000" /* 23 bytes, al zeroes */
4566 "0000000000000000000000"
4567 "%[var(check.username),hex]00" /* the username */
4568 "00" /* filler (always 0x00) */
4569 "010000" /* packet length*/
4570 "00" /* sequence ID */
4571 "01" /* COM_QUIT command */
4572 };
4573
4574 struct tcpcheck_ruleset *rs = NULL;
4575 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4576 struct tcpcheck_rule *chk;
4577 struct tcpcheck_var *var = NULL;
4578 char *mysql_rsname = "*mysql-check";
4579 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4580 int index = 0, err_code = 0;
4581
4582 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4583 err_code |= ERR_WARN;
4584
4585 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4586 goto out;
4587
4588 curpx->options2 &= ~PR_O2_CHK_ANY;
4589 curpx->options2 |= PR_O2_TCPCHK_CHK;
4590
4591 free_tcpcheck_vars(&rules->preset_vars);
4592 rules->list = NULL;
4593 rules->flags = 0;
4594
4595 cur_arg += 2;
4596 if (*args[cur_arg]) {
4597 int packetlen, userlen;
4598
4599 if (strcmp(args[cur_arg], "user") != 0) {
4600 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4601 file, line, args[0], args[1], args[cur_arg]);
4602 goto error;
4603 }
4604
4605 if (*(args[cur_arg+1]) == 0) {
4606 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4607 file, line, args[0], args[1], args[cur_arg]);
4608 goto error;
4609 }
4610
4611 hdr = calloc(4, sizeof(*hdr));
4612 user = strdup(args[cur_arg+1]);
4613 userlen = strlen(args[cur_arg+1]);
4614
4615 if (hdr == NULL || user == NULL) {
4616 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4617 goto error;
4618 }
4619
4620 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4621 packetlen = userlen + 7 + 27;
4622 mysql_req = mysql41_req;
4623 mysql_rsname = mysql41_rsname;
4624 }
4625 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4626 packetlen = userlen + 7;
4627 mysql_req = mysql40_req;
4628 mysql_rsname = mysql40_rsname;
4629 }
4630 else {
4631 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4632 file, line, args[cur_arg], args[cur_arg+2]);
4633 goto error;
4634 }
4635
4636 hdr[0] = (unsigned char)(packetlen & 0xff);
4637 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4638 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4639 hdr[3] = 1;
4640
4641 var = create_tcpcheck_var(ist("check.header"));
4642 if (var == NULL) {
4643 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4644 goto error;
4645 }
4646 var->data.type = SMP_T_STR;
4647 var->data.u.str.area = hdr;
4648 var->data.u.str.data = 4;
4649 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004650 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004651 hdr = NULL;
4652 var = NULL;
4653
4654 var = create_tcpcheck_var(ist("check.username"));
4655 if (var == NULL) {
4656 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4657 goto error;
4658 }
4659 var->data.type = SMP_T_STR;
4660 var->data.u.str.area = user;
4661 var->data.u.str.data = strlen(user);
4662 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004663 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004664 user = NULL;
4665 var = NULL;
4666 }
4667
4668 rs = find_tcpcheck_ruleset(mysql_rsname);
4669 if (rs)
4670 goto ruleset_found;
4671
4672 rs = create_tcpcheck_ruleset(mysql_rsname);
4673 if (rs == NULL) {
4674 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4675 goto error;
4676 }
4677
4678 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4679 1, curpx, &rs->rules, file, line, &errmsg);
4680 if (!chk) {
4681 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4682 goto error;
4683 }
4684 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004685 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004686
4687 if (mysql_req) {
4688 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4689 1, curpx, &rs->rules, file, line, &errmsg);
4690 if (!chk) {
4691 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4692 goto error;
4693 }
4694 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004695 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004696 }
4697
4698 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4699 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4700 if (!chk) {
4701 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4702 goto error;
4703 }
4704 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4705 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004706 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004707
4708 if (mysql_req) {
4709 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4710 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4711 if (!chk) {
4712 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4713 goto error;
4714 }
4715 chk->expect.custom = tcpcheck_mysql_expect_ok;
4716 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004717 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004718 }
4719
4720 ruleset_found:
4721 rules->list = &rs->rules;
4722 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4723 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4724
4725 out:
4726 free(errmsg);
4727 return err_code;
4728
4729 error:
4730 free(hdr);
4731 free(user);
4732 free(var);
4733 free_tcpcheck_vars(&rules->preset_vars);
4734 free_tcpcheck_ruleset(rs);
4735 err_code |= ERR_ALERT | ERR_FATAL;
4736 goto out;
4737}
4738
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004739int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004740 const char *file, int line)
4741{
4742 static char *ldap_req = "300C020101600702010304008000";
4743
4744 struct tcpcheck_ruleset *rs = NULL;
4745 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4746 struct tcpcheck_rule *chk;
4747 char *errmsg = NULL;
4748 int err_code = 0;
4749
4750 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4751 err_code |= ERR_WARN;
4752
4753 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4754 goto out;
4755
4756 curpx->options2 &= ~PR_O2_CHK_ANY;
4757 curpx->options2 |= PR_O2_TCPCHK_CHK;
4758
4759 free_tcpcheck_vars(&rules->preset_vars);
4760 rules->list = NULL;
4761 rules->flags = 0;
4762
4763 rs = find_tcpcheck_ruleset("*ldap-check");
4764 if (rs)
4765 goto ruleset_found;
4766
4767 rs = create_tcpcheck_ruleset("*ldap-check");
4768 if (rs == NULL) {
4769 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4770 goto error;
4771 }
4772
4773 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4774 1, curpx, &rs->rules, file, line, &errmsg);
4775 if (!chk) {
4776 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4777 goto error;
4778 }
4779 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004780 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004781
4782 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4783 "min-recv", "14",
4784 "on-error", "Not LDAPv3 protocol",
4785 ""},
4786 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4787 if (!chk) {
4788 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4789 goto error;
4790 }
4791 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004792 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004793
4794 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4795 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4796 if (!chk) {
4797 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4798 goto error;
4799 }
4800 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4801 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004802 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004803
4804 ruleset_found:
4805 rules->list = &rs->rules;
4806 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4807 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4808
4809 out:
4810 free(errmsg);
4811 return err_code;
4812
4813 error:
4814 free_tcpcheck_ruleset(rs);
4815 err_code |= ERR_ALERT | ERR_FATAL;
4816 goto out;
4817}
4818
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004819int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004820 const char *file, int line)
4821{
4822 struct tcpcheck_ruleset *rs = NULL;
4823 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4824 struct tcpcheck_rule *chk;
4825 char *spop_req = NULL;
4826 char *errmsg = NULL;
4827 int spop_len = 0, err_code = 0;
4828
4829 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4830 err_code |= ERR_WARN;
4831
4832 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4833 goto out;
4834
4835 curpx->options2 &= ~PR_O2_CHK_ANY;
4836 curpx->options2 |= PR_O2_TCPCHK_CHK;
4837
4838 free_tcpcheck_vars(&rules->preset_vars);
4839 rules->list = NULL;
4840 rules->flags = 0;
4841
4842
4843 rs = find_tcpcheck_ruleset("*spop-check");
4844 if (rs)
4845 goto ruleset_found;
4846
4847 rs = create_tcpcheck_ruleset("*spop-check");
4848 if (rs == NULL) {
4849 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4850 goto error;
4851 }
4852
4853 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4854 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4855 goto error;
4856 }
4857 chunk_reset(&trash);
4858 dump_binary(&trash, spop_req, spop_len);
4859 trash.area[trash.data] = '\0';
4860
4861 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4862 1, curpx, &rs->rules, file, line, &errmsg);
4863 if (!chk) {
4864 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4865 goto error;
4866 }
4867 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004868 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004869
4870 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4871 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4872 if (!chk) {
4873 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4874 goto error;
4875 }
4876 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4877 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004878 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004879
4880 ruleset_found:
4881 rules->list = &rs->rules;
4882 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4883 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4884
4885 out:
4886 free(spop_req);
4887 free(errmsg);
4888 return err_code;
4889
4890 error:
4891 free_tcpcheck_ruleset(rs);
4892 err_code |= ERR_ALERT | ERR_FATAL;
4893 goto out;
4894}
4895
4896
4897static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4898{
4899 struct tcpcheck_rule *chk = NULL;
4900 struct tcpcheck_http_hdr *hdr = NULL;
4901 char *meth = NULL, *uri = NULL, *vsn = NULL;
4902 char *hdrs, *body;
4903
4904 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4905 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4906 if (hdrs == body)
4907 hdrs = NULL;
4908 if (hdrs) {
4909 *hdrs = '\0';
4910 hdrs +=2;
4911 }
4912 if (body) {
4913 *body = '\0';
4914 body += 4;
4915 }
4916 if (hdrs || body) {
4917 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4918 " Please, consider to use 'http-check send' directive instead.");
4919 }
4920
4921 chk = calloc(1, sizeof(*chk));
4922 if (!chk) {
4923 memprintf(errmsg, "out of memory");
4924 goto error;
4925 }
4926 chk->action = TCPCHK_ACT_SEND;
4927 chk->send.type = TCPCHK_SEND_HTTP;
4928 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4929 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4930 LIST_INIT(&chk->send.http.hdrs);
4931
4932 /* Copy the method, uri and version */
4933 if (*args[cur_arg]) {
4934 if (!*args[cur_arg+1])
4935 uri = args[cur_arg];
4936 else
4937 meth = args[cur_arg];
4938 }
4939 if (*args[cur_arg+1])
4940 uri = args[cur_arg+1];
4941 if (*args[cur_arg+2])
4942 vsn = args[cur_arg+2];
4943
4944 if (meth) {
4945 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4946 chk->send.http.meth.str.area = strdup(meth);
4947 chk->send.http.meth.str.data = strlen(meth);
4948 if (!chk->send.http.meth.str.area) {
4949 memprintf(errmsg, "out of memory");
4950 goto error;
4951 }
4952 }
4953 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004954 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004955 if (!isttest(chk->send.http.uri)) {
4956 memprintf(errmsg, "out of memory");
4957 goto error;
4958 }
4959 }
4960 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004961 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004962 if (!isttest(chk->send.http.vsn)) {
4963 memprintf(errmsg, "out of memory");
4964 goto error;
4965 }
4966 }
4967
4968 /* Copy the header */
4969 if (hdrs) {
4970 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4971 struct h1m h1m;
4972 int i, ret;
4973
4974 /* Build and parse the request */
4975 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4976
4977 h1m.flags = H1_MF_HDRS_ONLY;
4978 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4979 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4980 &h1m, NULL);
4981 if (ret <= 0) {
4982 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4983 goto error;
4984 }
4985
4986 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4987 hdr = calloc(1, sizeof(*hdr));
4988 if (!hdr) {
4989 memprintf(errmsg, "out of memory");
4990 goto error;
4991 }
4992 LIST_INIT(&hdr->value);
4993 hdr->name = istdup(tmp_hdrs[i].n);
4994 if (!hdr->name.ptr) {
4995 memprintf(errmsg, "out of memory");
4996 goto error;
4997 }
4998
4999 ist0(tmp_hdrs[i].v);
5000 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5001 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005002 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005003 }
5004 }
5005
5006 /* Copy the body */
5007 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005008 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005009 if (!isttest(chk->send.http.body)) {
5010 memprintf(errmsg, "out of memory");
5011 goto error;
5012 }
5013 }
5014
5015 return chk;
5016
5017 error:
5018 free_tcpcheck_http_hdr(hdr);
5019 free_tcpcheck(chk, 0);
5020 return NULL;
5021}
5022
5023/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005024int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005025 const char *file, int line)
5026{
5027 struct tcpcheck_ruleset *rs = NULL;
5028 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5029 struct tcpcheck_rule *chk;
5030 char *errmsg = NULL;
5031 int err_code = 0;
5032
5033 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5034 err_code |= ERR_WARN;
5035
5036 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5037 goto out;
5038
5039 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5040 if (!chk) {
5041 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5042 goto error;
5043 }
5044 if (errmsg) {
5045 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5046 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005047 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005048 }
5049
5050 no_request:
5051 curpx->options2 &= ~PR_O2_CHK_ANY;
5052 curpx->options2 |= PR_O2_TCPCHK_CHK;
5053
5054 free_tcpcheck_vars(&rules->preset_vars);
5055 rules->list = NULL;
5056 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5057
5058 /* Deduce the ruleset name from the proxy info */
5059 chunk_printf(&trash, "*http-check-%s_%s-%d",
5060 ((curpx == defpx) ? "defaults" : curpx->id),
5061 curpx->conf.file, curpx->conf.line);
5062
5063 rs = find_tcpcheck_ruleset(b_orig(&trash));
5064 if (rs == NULL) {
5065 rs = create_tcpcheck_ruleset(b_orig(&trash));
5066 if (rs == NULL) {
5067 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5068 goto error;
5069 }
5070 }
5071
5072 rules->list = &rs->rules;
5073 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5074 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5075 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5076 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5077 rules->list = NULL;
5078 goto error;
5079 }
5080
5081 out:
5082 free(errmsg);
5083 return err_code;
5084
5085 error:
5086 free_tcpcheck_ruleset(rs);
5087 free_tcpcheck(chk, 0);
5088 err_code |= ERR_ALERT | ERR_FATAL;
5089 goto out;
5090}
5091
5092/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005093int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005094 const char *file, int line)
5095{
5096 struct tcpcheck_ruleset *rs = NULL;
5097 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5098 int err_code = 0;
5099
5100 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5101 err_code |= ERR_WARN;
5102
5103 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5104 goto out;
5105
5106 curpx->options2 &= ~PR_O2_CHK_ANY;
5107 curpx->options2 |= PR_O2_TCPCHK_CHK;
5108
5109 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5110 /* If a tcp-check rulesset is already set, do nothing */
5111 if (rules->list)
5112 goto out;
5113
5114 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5115 * get it.
5116 */
5117 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5118 goto curpx_ruleset;
5119
5120 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5121 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5122 rs = find_tcpcheck_ruleset(b_orig(&trash));
5123 if (rs)
5124 goto ruleset_found;
5125 }
5126
5127 curpx_ruleset:
5128 /* Deduce the ruleset name from the proxy info */
5129 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5130 ((curpx == defpx) ? "defaults" : curpx->id),
5131 curpx->conf.file, curpx->conf.line);
5132
5133 rs = find_tcpcheck_ruleset(b_orig(&trash));
5134 if (rs == NULL) {
5135 rs = create_tcpcheck_ruleset(b_orig(&trash));
5136 if (rs == NULL) {
5137 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5138 goto error;
5139 }
5140 }
5141
5142 ruleset_found:
5143 free_tcpcheck_vars(&rules->preset_vars);
5144 rules->list = &rs->rules;
5145 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5146 rules->flags |= TCPCHK_RULES_TCP_CHK;
5147
5148 out:
5149 return err_code;
5150
5151 error:
5152 err_code |= ERR_ALERT | ERR_FATAL;
5153 goto out;
5154}
5155
Willy Tarreau51cd5952020-06-05 12:25:38 +02005156static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005157 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005158 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5159 { 0, NULL, NULL },
5160}};
5161
5162REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5163REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5164REGISTER_POST_DEINIT(deinit_tcpchecks);
5165INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);