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