blob: 294e49bcc86bb3f10701d71f32ff9bff1fb78716 [file] [log] [blame]
Willy Tarreau51cd5952020-06-05 12:25:38 +02001/*
2 * Health-checks functions.
3 *
4 * Copyright 2000-2009,2020 Willy Tarreau <w@1wt.eu>
5 * Copyright 2007-2010 Krzysztof Piotr Oledzki <ole@ans.pl>
6 * Copyright 2013 Baptiste Assmann <bedis9@gmail.com>
7 * Copyright 2020 Gaetan Rivet <grive@u256.net>
8 * Copyright 2020 Christopher Faulet <cfaulet@haproxy.com>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version
13 * 2 of the License, or (at your option) any later version.
14 *
15 */
16
17#include <sys/resource.h>
18#include <sys/socket.h>
19#include <sys/types.h>
20#include <sys/wait.h>
21#include <netinet/in.h>
22#include <netinet/tcp.h>
23#include <arpa/inet.h>
24
25#include <ctype.h>
26#include <errno.h>
27#include <fcntl.h>
28#include <signal.h>
29#include <stdarg.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <time.h>
34#include <unistd.h>
35
36#include <haproxy/action.h>
37#include <haproxy/api.h>
38#include <haproxy/cfgparse.h>
39#include <haproxy/check.h>
40#include <haproxy/chunk.h>
41#include <haproxy/connection.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020042#include <haproxy/errors.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020043#include <haproxy/global.h>
44#include <haproxy/h1.h>
45#include <haproxy/http.h>
46#include <haproxy/http_htx.h>
47#include <haproxy/htx.h>
48#include <haproxy/istbuf.h>
49#include <haproxy/list.h>
50#include <haproxy/log.h>
Christopher Faulet8a0e5f82021-09-16 16:01:09 +020051#include <haproxy/net_helper.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020052#include <haproxy/protocol.h>
53#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020054#include <haproxy/regex.h>
55#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020056#include <haproxy/server.h>
57#include <haproxy/ssl_sock.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020058#include <haproxy/task.h>
59#include <haproxy/tcpcheck.h>
Willy Tarreau9310f482021-10-06 16:18:40 +020060#include <haproxy/ticks.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020061#include <haproxy/tools.h>
Christopher Faulet147b8c92021-04-10 09:00:38 +020062#include <haproxy/trace.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020063#include <haproxy/vars.h>
64
65
Christopher Faulet147b8c92021-04-10 09:00:38 +020066#define TRACE_SOURCE &trace_check
67
Willy Tarreau51cd5952020-06-05 12:25:38 +020068/* Global tree to share all tcp-checks */
69struct eb_root shared_tcpchecks = EB_ROOT;
70
71
72DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
73
74/**************************************************************************/
75/*************** Init/deinit tcp-check rules and ruleset ******************/
76/**************************************************************************/
77/* Releases memory allocated for a log-format string */
78static void free_tcpcheck_fmt(struct list *fmt)
79{
80 struct logformat_node *lf, *lfb;
81
82 list_for_each_entry_safe(lf, lfb, fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +020083 LIST_DELETE(&lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +020084 release_sample_expr(lf->expr);
85 free(lf->arg);
86 free(lf);
87 }
88}
89
90/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
91void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
92{
93 if (!hdr)
94 return;
95
96 free_tcpcheck_fmt(&hdr->value);
97 istfree(&hdr->name);
98 free(hdr);
99}
100
101/* Releases memory allocated for an HTTP header list used in a tcp-check send
102 * rule
103 */
104static void free_tcpcheck_http_hdrs(struct list *hdrs)
105{
106 struct tcpcheck_http_hdr *hdr, *bhdr;
107
108 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200109 LIST_DELETE(&hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200110 free_tcpcheck_http_hdr(hdr);
111 }
112}
113
114/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
115 * tcp-check was allocated using a memory pool (it is used to instantiate email
116 * alerts).
117 */
118void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
119{
120 if (!rule)
121 return;
122
123 free(rule->comment);
124 switch (rule->action) {
125 case TCPCHK_ACT_SEND:
126 switch (rule->send.type) {
127 case TCPCHK_SEND_STRING:
128 case TCPCHK_SEND_BINARY:
129 istfree(&rule->send.data);
130 break;
131 case TCPCHK_SEND_STRING_LF:
132 case TCPCHK_SEND_BINARY_LF:
133 free_tcpcheck_fmt(&rule->send.fmt);
134 break;
135 case TCPCHK_SEND_HTTP:
136 free(rule->send.http.meth.str.area);
137 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
138 istfree(&rule->send.http.uri);
139 else
140 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
141 istfree(&rule->send.http.vsn);
142 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
143 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
144 istfree(&rule->send.http.body);
145 else
146 free_tcpcheck_fmt(&rule->send.http.body_fmt);
147 break;
148 case TCPCHK_SEND_UNDEF:
149 break;
150 }
151 break;
152 case TCPCHK_ACT_EXPECT:
153 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
154 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
155 release_sample_expr(rule->expect.status_expr);
156 switch (rule->expect.type) {
157 case TCPCHK_EXPECT_HTTP_STATUS:
158 free(rule->expect.codes.codes);
159 break;
160 case TCPCHK_EXPECT_STRING:
161 case TCPCHK_EXPECT_BINARY:
162 case TCPCHK_EXPECT_HTTP_BODY:
163 istfree(&rule->expect.data);
164 break;
165 case TCPCHK_EXPECT_STRING_REGEX:
166 case TCPCHK_EXPECT_BINARY_REGEX:
167 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
168 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
169 regex_free(rule->expect.regex);
170 break;
171 case TCPCHK_EXPECT_STRING_LF:
172 case TCPCHK_EXPECT_BINARY_LF:
173 case TCPCHK_EXPECT_HTTP_BODY_LF:
174 free_tcpcheck_fmt(&rule->expect.fmt);
175 break;
176 case TCPCHK_EXPECT_HTTP_HEADER:
177 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
178 regex_free(rule->expect.hdr.name_re);
179 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
180 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
181 else
182 istfree(&rule->expect.hdr.name);
183
184 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
185 regex_free(rule->expect.hdr.value_re);
186 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
187 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
188 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
189 istfree(&rule->expect.hdr.value);
190 break;
191 case TCPCHK_EXPECT_CUSTOM:
192 case TCPCHK_EXPECT_UNDEF:
193 break;
194 }
195 break;
196 case TCPCHK_ACT_CONNECT:
197 free(rule->connect.sni);
198 free(rule->connect.alpn);
199 release_sample_expr(rule->connect.port_expr);
200 break;
201 case TCPCHK_ACT_COMMENT:
202 break;
203 case TCPCHK_ACT_ACTION_KW:
204 free(rule->action_kw.rule);
205 break;
206 }
207
208 if (in_pool)
209 pool_free(pool_head_tcpcheck_rule, rule);
210 else
211 free(rule);
212}
213
214/* Creates a tcp-check variable used in preset variables before executing a
215 * tcp-check ruleset.
216 */
217struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
218{
219 struct tcpcheck_var *var = NULL;
220
221 var = calloc(1, sizeof(*var));
222 if (var == NULL)
223 return NULL;
224
225 var->name = istdup(name);
226 if (!isttest(var->name)) {
227 free(var);
228 return NULL;
229 }
230
231 LIST_INIT(&var->list);
232 return var;
233}
234
235/* Releases memory allocated for a preset tcp-check variable */
236void free_tcpcheck_var(struct tcpcheck_var *var)
237{
238 if (!var)
239 return;
240
241 istfree(&var->name);
242 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
243 free(var->data.u.str.area);
244 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
245 free(var->data.u.meth.str.area);
246 free(var);
247}
248
249/* Releases a list of preset tcp-check variables */
250void free_tcpcheck_vars(struct list *vars)
251{
252 struct tcpcheck_var *var, *back;
253
254 list_for_each_entry_safe(var, back, vars, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200255 LIST_DELETE(&var->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200256 free_tcpcheck_var(var);
257 }
258}
259
260/* Duplicate a list of preset tcp-check variables */
Willy Tarreau09f2e772021-02-12 08:42:30 +0100261int dup_tcpcheck_vars(struct list *dst, const struct list *src)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200262{
Willy Tarreau09f2e772021-02-12 08:42:30 +0100263 const struct tcpcheck_var *var;
264 struct tcpcheck_var *new = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200265
266 list_for_each_entry(var, src, list) {
267 new = create_tcpcheck_var(var->name);
268 if (!new)
269 goto error;
270 new->data.type = var->data.type;
271 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
272 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
273 goto error;
274 if (var->data.type == SMP_T_STR)
275 new->data.u.str.area[new->data.u.str.data] = 0;
276 }
277 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
278 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
279 goto error;
280 new->data.u.str.area[new->data.u.str.data] = 0;
281 new->data.u.meth.meth = var->data.u.meth.meth;
282 }
283 else
284 new->data.u = var->data.u;
Willy Tarreau2b718102021-04-21 07:32:39 +0200285 LIST_APPEND(dst, &new->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200286 }
287 return 1;
288
289 error:
290 free(new);
291 return 0;
292}
293
294/* Looks for a shared tcp-check ruleset given its name. */
295struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
296{
297 struct tcpcheck_ruleset *rs;
298 struct ebpt_node *node;
299
300 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
301 if (node) {
302 rs = container_of(node, typeof(*rs), node);
303 return rs;
304 }
305 return NULL;
306}
307
308/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks
309 * tree.
310 */
311struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
312{
313 struct tcpcheck_ruleset *rs;
314
315 rs = calloc(1, sizeof(*rs));
316 if (rs == NULL)
317 return NULL;
318
319 rs->node.key = strdup(name);
320 if (rs->node.key == NULL) {
321 free(rs);
322 return NULL;
323 }
324
325 LIST_INIT(&rs->rules);
326 ebis_insert(&shared_tcpchecks, &rs->node);
327 return rs;
328}
329
330/* Releases memory allocated by a tcp-check ruleset. */
331void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
332{
333 struct tcpcheck_rule *r, *rb;
334
335 if (!rs)
336 return;
337
338 ebpt_delete(&rs->node);
339 free(rs->node.key);
340 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200341 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200342 free_tcpcheck(r, 0);
343 }
344 free(rs);
345}
346
347
348/**************************************************************************/
349/**************** Everything about tcp-checks execution *******************/
350/**************************************************************************/
351/* Returns the id of a step in a tcp-check ruleset */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200352int tcpcheck_get_step_id(const struct check *check, const struct tcpcheck_rule *rule)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200353{
354 if (!rule)
355 rule = check->current_step;
356
357 /* no last started step => first step */
358 if (!rule)
359 return 1;
360
361 /* last step is the first implicit connect */
362 if (rule->index == 0 &&
363 rule->action == TCPCHK_ACT_CONNECT &&
364 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
365 return 0;
366
367 return rule->index + 1;
368}
369
370/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
371 * NULL if none was found.
372 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200373struct tcpcheck_rule *get_first_tcpcheck_rule(const struct tcpcheck_rules *rules)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200374{
375 struct tcpcheck_rule *r;
376
377 list_for_each_entry(r, rules->list, list) {
378 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
379 return r;
380 }
381 return NULL;
382}
383
384/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
385 * NULL if none was found.
386 */
387static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
388{
389 struct tcpcheck_rule *r;
390
391 list_for_each_entry_rev(r, rules->list, list) {
392 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
393 return r;
394 }
395 return NULL;
396}
397
398/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
399 * <start> or NULL if non was found. If <start> is NULL, it relies on
400 * get_first_tcpcheck_rule().
401 */
402static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
403{
404 struct tcpcheck_rule *r;
405
406 if (!start)
407 return get_first_tcpcheck_rule(rules);
408
409 r = LIST_NEXT(&start->list, typeof(r), list);
410 list_for_each_entry_from(r, rules->list, list) {
411 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
412 return r;
413 }
414 return NULL;
415}
416
417
418/* Creates info message when a tcp-check healthcheck fails on an expect rule */
419static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
420 int match, struct ist info)
421{
422 struct sample *smp;
423
424 /* Follows these step to produce the info message:
425 * 1. if info field is already provided, copy it
426 * 2. if the expect rule provides an onerror log-format string,
427 * use it to produce the message
428 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
429 * 4. Otherwise produce the generic tcp-check info message
430 */
431 if (istlen(info)) {
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100432 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200433 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))
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100520 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200521 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
522 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
523 &rule->expect.onsuccess_fmt);
524 else if (check->type == PR_O2_TCPCHK_CHK &&
525 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
526 chunk_strcat(msg, "(tcp-check)");
527
528 /* Finally, the check status code is set if the expect rule defines a
529 * status expression.
530 */
531 if (rule->expect.status_expr) {
532 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
533 rule->expect.status_expr, SMP_T_STR);
534
535 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
536 sample_casts[smp->data.type][SMP_T_SINT](smp))
537 check->code = smp->data.u.sint;
538 }
539
540 *(b_tail(msg)) = '\0';
541}
542
543/* Internal functions to parse and validate a MySQL packet in the context of an
544 * expect rule. It start to parse the input buffer at the offset <offset>. If
545 * <last_read> is set, no more data are expected.
546 */
547static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
548 unsigned int offset, int last_read)
549{
550 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
551 enum healthcheck_status status;
552 struct buffer *msg = NULL;
553 struct ist desc = IST_NULL;
554 unsigned int err = 0, plen = 0;
555
556
Christopher Faulet147b8c92021-04-10 09:00:38 +0200557 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
558
Willy Tarreau51cd5952020-06-05 12:25:38 +0200559 /* 3 Bytes for the packet length and 1 byte for the sequence id */
560 if (b_data(&check->bi) < offset+4) {
561 if (!last_read)
562 goto wait_more_data;
563
564 /* invalid length or truncated response */
565 status = HCHK_STATUS_L7RSP;
566 goto error;
567 }
568
569 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
570 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
571 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
572
573 if (b_data(&check->bi) < offset+plen+4) {
574 if (!last_read)
575 goto wait_more_data;
576
577 /* invalid length or truncated response */
578 status = HCHK_STATUS_L7RSP;
579 goto error;
580 }
581
582 if (*b_peek(&check->bi, offset+4) == '\xff') {
583 /* MySQL Error packet always begin with field_count = 0xff */
584 status = HCHK_STATUS_L7STS;
585 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
586 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
587 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
588 goto error;
589 }
590
591 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
592 /* Not the last rule, continue */
593 goto out;
594 }
595
596 /* We set the MySQL Version in description for information purpose
597 * FIXME : it can be cool to use MySQL Version for other purpose,
598 * like mark as down old MySQL server.
599 */
600 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
601 set_server_check_status(check, status, b_peek(&check->bi, 5));
602
603 out:
604 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200605 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200606 return ret;
607
608 error:
609 ret = TCPCHK_EVAL_STOP;
610 check->code = err;
611 msg = alloc_trash_chunk();
612 if (msg)
613 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
614 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
615 goto out;
616
617 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200618 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200619 ret = TCPCHK_EVAL_WAIT;
620 goto out;
621}
622
623/* Custom tcp-check expect function to parse and validate the MySQL initial
624 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
625 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
626 * error occurred.
627 */
628enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
629{
630 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
631}
632
633/* Custom tcp-check expect function to parse and validate the MySQL OK packet
634 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
635 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
636 * an error occurred.
637 */
638enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
639{
640 unsigned int hslen = 0;
641
642 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
643 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
644 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
645
646 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
647}
648
649/* Custom tcp-check expect function to parse and validate the LDAP bind response
650 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
651 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
652 * error occurred.
653 */
654enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
655{
656 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
657 enum healthcheck_status status;
658 struct buffer *msg = NULL;
659 struct ist desc = IST_NULL;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200660 char *ptr;
661 unsigned short nbytes = 0;
662 size_t msglen = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200663
Christopher Faulet147b8c92021-04-10 09:00:38 +0200664 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
665
Willy Tarreau51cd5952020-06-05 12:25:38 +0200666 /* Check if the server speaks LDAP (ASN.1/BER)
667 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
668 * http://tools.ietf.org/html/rfc4511
669 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200670 ptr = b_head(&check->bi) + 1;
671
Willy Tarreau51cd5952020-06-05 12:25:38 +0200672 /* size of LDAPMessage */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200673 if (*ptr & 0x80) {
674 /* For message size encoded on several bytes, we only handle
675 * size encoded on 2 or 4 bytes. There is no reason to make this
676 * part to complex because only Active Directory is known to
677 * encode BindReponse length on 4 bytes.
678 */
679 nbytes = (*ptr & 0x7f);
680 if (b_data(&check->bi) < 1 + nbytes)
681 goto too_short;
682 switch (nbytes) {
683 case 4: msglen = read_n32(ptr+1); break;
684 case 2: msglen = read_n16(ptr+1); break;
685 default:
686 status = HCHK_STATUS_L7RSP;
687 desc = ist("Not LDAPv3 protocol");
688 goto error;
689 }
690 }
691 else
692 msglen = *ptr;
693 ptr += 1 + nbytes;
694
695 if (b_data(&check->bi) < 2 + nbytes + msglen)
696 goto too_short;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200697
698 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
699 * messageID: 0x02 0x01 0x01: INTEGER 1
700 * protocolOp: 0x61: bindResponse
701 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200702 if (memcmp(ptr, "\x02\x01\x01\x61", 4) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200703 status = HCHK_STATUS_L7RSP;
704 desc = ist("Not LDAPv3 protocol");
705 goto error;
706 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200707 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200708
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200709 /* skip size of bindResponse */
710 nbytes = 0;
711 if (*ptr & 0x80)
712 nbytes = (*ptr & 0x7f);
713 ptr += 1 + nbytes;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200714
715 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
716 * ldapResult: 0x0a 0x01: ENUMERATION
717 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200718 if (memcmp(ptr, "\x0a\x01", 2) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200719 status = HCHK_STATUS_L7RSP;
720 desc = ist("Not LDAPv3 protocol");
721 goto error;
722 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200723 ptr += 2;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200724
725 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
726 * resultCode
727 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200728 check->code = *ptr;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200729 if (check->code) {
730 status = HCHK_STATUS_L7STS;
731 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
732 goto error;
733 }
734
735 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
736 set_server_check_status(check, status, "Success");
737
738 out:
739 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200740 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200741 return ret;
742
743 error:
744 ret = TCPCHK_EVAL_STOP;
745 msg = alloc_trash_chunk();
746 if (msg)
747 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
748 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
749 goto out;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200750
751 too_short:
752 if (!last_read)
753 goto wait_more_data;
754 /* invalid length or truncated response */
755 status = HCHK_STATUS_L7RSP;
756 goto error;
757
758 wait_more_data:
759 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
760 ret = TCPCHK_EVAL_WAIT;
761 goto out;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200762}
763
764/* Custom tcp-check expect function to parse and validate the SPOP hello agent
765 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
766 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
767 */
768enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
769{
770 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
771 enum healthcheck_status status;
772 struct buffer *msg = NULL;
773 struct ist desc = IST_NULL;
774 unsigned int framesz;
775
Christopher Faulet147b8c92021-04-10 09:00:38 +0200776 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200777
778 memcpy(&framesz, b_head(&check->bi), 4);
779 framesz = ntohl(framesz);
780
781 if (!last_read && b_data(&check->bi) < (4+framesz))
782 goto wait_more_data;
783
784 memset(b_orig(&trash), 0, b_size(&trash));
785 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
786 status = HCHK_STATUS_L7RSP;
787 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
788 goto error;
789 }
790
791 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
792 set_server_check_status(check, status, "SPOA server is ok");
793
794 out:
795 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200796 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200797 return ret;
798
799 error:
800 ret = TCPCHK_EVAL_STOP;
801 msg = alloc_trash_chunk();
802 if (msg)
803 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
804 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
805 goto out;
806
807 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200808 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200809 ret = TCPCHK_EVAL_WAIT;
810 goto out;
811}
812
813/* Custom tcp-check expect function to parse and validate the agent-check
814 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
815 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
816 */
817enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
818{
819 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
820 enum healthcheck_status status = HCHK_STATUS_CHECKED;
821 const char *hs = NULL; /* health status */
822 const char *as = NULL; /* admin status */
823 const char *ps = NULL; /* performance status */
824 const char *cs = NULL; /* maxconn */
825 const char *err = NULL; /* first error to report */
826 const char *wrn = NULL; /* first warning to report */
827 char *cmd, *p;
828
Christopher Faulet147b8c92021-04-10 09:00:38 +0200829 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
830
Willy Tarreau51cd5952020-06-05 12:25:38 +0200831 /* We're getting an agent check response. The agent could
832 * have been disabled in the mean time with a long check
833 * still pending. It is important that we ignore the whole
834 * response.
835 */
836 if (!(check->state & CHK_ST_ENABLED))
837 goto out;
838
839 /* The agent supports strings made of a single line ended by the
840 * first CR ('\r') or LF ('\n'). This line is composed of words
841 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
842 * line may optionally contained a description of a state change
843 * after a sharp ('#'), which is only considered if a health state
844 * is announced.
845 *
846 * Words may be composed of :
847 * - a numeric weight suffixed by the percent character ('%').
848 * - a health status among "up", "down", "stopped", and "fail".
849 * - an admin status among "ready", "drain", "maint".
850 *
851 * These words may appear in any order. If multiple words of the
852 * same category appear, the last one wins.
853 */
854
855 p = b_head(&check->bi);
856 while (*p && *p != '\n' && *p != '\r')
857 p++;
858
859 if (!*p) {
860 if (!last_read)
861 goto wait_more_data;
862
863 /* at least inform the admin that the agent is mis-behaving */
864 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
865 goto out;
866 }
867
868 *p = 0;
869 cmd = b_head(&check->bi);
870
871 while (*cmd) {
872 /* look for next word */
873 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
874 cmd++;
875 continue;
876 }
877
878 if (*cmd == '#') {
879 /* this is the beginning of a health status description,
880 * skip the sharp and blanks.
881 */
882 cmd++;
883 while (*cmd == '\t' || *cmd == ' ')
884 cmd++;
885 break;
886 }
887
888 /* find the end of the word so that we have a null-terminated
889 * word between <cmd> and <p>.
890 */
891 p = cmd + 1;
892 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
893 p++;
894 if (*p)
895 *p++ = 0;
896
897 /* first, health statuses */
898 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100899 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200900 status = HCHK_STATUS_L7OKD;
901 hs = cmd;
902 }
903 else if (strcasecmp(cmd, "down") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100904 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200905 status = HCHK_STATUS_L7STS;
906 hs = cmd;
907 }
908 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100909 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200910 status = HCHK_STATUS_L7STS;
911 hs = cmd;
912 }
913 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100914 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200915 status = HCHK_STATUS_L7STS;
916 hs = cmd;
917 }
918 /* admin statuses */
919 else if (strcasecmp(cmd, "ready") == 0) {
920 as = cmd;
921 }
922 else if (strcasecmp(cmd, "drain") == 0) {
923 as = cmd;
924 }
925 else if (strcasecmp(cmd, "maint") == 0) {
926 as = cmd;
927 }
928 /* try to parse a weight here and keep the last one */
929 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
930 ps = cmd;
931 }
932 /* try to parse a maxconn here */
933 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
934 cs = cmd;
935 }
936 else {
937 /* keep a copy of the first error */
938 if (!err)
939 err = cmd;
940 }
941 /* skip to next word */
942 cmd = p;
943 }
944 /* here, cmd points either to \0 or to the beginning of a
945 * description. Skip possible leading spaces.
946 */
947 while (*cmd == ' ' || *cmd == '\n')
948 cmd++;
949
950 /* First, update the admin status so that we avoid sending other
951 * possibly useless warnings and can also update the health if
952 * present after going back up.
953 */
954 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200955 if (strcasecmp(as, "drain") == 0) {
956 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200957 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200958 }
959 else if (strcasecmp(as, "maint") == 0) {
960 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200961 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200962 }
963 else {
964 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200965 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200966 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200967 }
968
969 /* now change weights */
970 if (ps) {
971 const char *msg;
972
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500973 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200974 msg = server_parse_weight_change_request(check->server, ps);
975 if (!wrn || !*wrn)
976 wrn = msg;
977 }
978
979 if (cs) {
980 const char *msg;
981
982 cs += strlen("maxconn:");
983
Christopher Faulet147b8c92021-04-10 09:00:38 +0200984 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury Denoyelle02742862021-06-18 11:11:36 +0200985 /* This is safe to call server_parse_maxconn_change_request
986 * because the server lock is held during the check.
987 */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200988 msg = server_parse_maxconn_change_request(check->server, cs);
989 if (!wrn || !*wrn)
990 wrn = msg;
991 }
992
993 /* and finally health status */
994 if (hs) {
995 /* We'll report some of the warnings and errors we have
996 * here. Down reports are critical, we leave them untouched.
997 * Lack of report, or report of 'UP' leaves the room for
998 * ERR first, then WARN.
999 */
1000 const char *msg = cmd;
1001 struct buffer *t;
1002
1003 if (!*msg || status == HCHK_STATUS_L7OKD) {
1004 if (err && *err)
1005 msg = err;
1006 else if (wrn && *wrn)
1007 msg = wrn;
1008 }
1009
1010 t = get_trash_chunk();
1011 chunk_printf(t, "via agent : %s%s%s%s",
1012 hs, *msg ? " (" : "",
1013 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001014 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001015 set_server_check_status(check, status, t->area);
1016 }
1017 else if (err && *err) {
1018 /* No status change but we'd like to report something odd.
1019 * Just report the current state and copy the message.
1020 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001021 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001022 chunk_printf(&trash, "agent reports an error : %s", err);
1023 set_server_check_status(check, status/*check->status*/, trash.area);
1024 }
1025 else if (wrn && *wrn) {
1026 /* No status change but we'd like to report something odd.
1027 * Just report the current state and copy the message.
1028 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001029 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001030 chunk_printf(&trash, "agent warns : %s", wrn);
1031 set_server_check_status(check, status/*check->status*/, trash.area);
1032 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001033 else {
1034 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001035 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001036 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001037
1038 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001039 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001040 return ret;
1041
1042 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001043 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001044 ret = TCPCHK_EVAL_WAIT;
1045 goto out;
1046}
1047
1048/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1049 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1050 * TCPCHK_EVAL_STOP if an error occurred.
1051 */
1052enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1053{
1054 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1055 struct tcpcheck_connect *connect = &rule->connect;
1056 struct proxy *proxy = check->proxy;
1057 struct server *s = check->server;
1058 struct task *t = check->task;
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001059 struct conn_stream *cs = check->cs;
1060 struct connection *conn = cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001061 struct protocol *proto;
1062 struct xprt_ops *xprt;
1063 struct tcpcheck_rule *next;
1064 int status, port;
1065
Christopher Faulet147b8c92021-04-10 09:00:38 +02001066 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1067
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001068 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1069
1070 /* current connection already created, check if it is established or not */
1071 if (conn) {
1072 if (conn->flags & CO_FL_WAIT_XPRT) {
1073 /* We are still waiting for the connection establishment */
1074 if (next && next->action == TCPCHK_ACT_SEND) {
1075 if (!(check->wait_list.events & SUB_RETRY_SEND))
1076 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1077 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001078 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001079 }
1080 else
1081 ret = tcpcheck_eval_recv(check, rule);
1082 }
1083 goto out;
1084 }
1085
1086 /* Note: here check->cs = cs = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001087
Christopher Fauletb381a502020-11-25 13:47:00 +01001088 /* Always release input and output buffer when a new connect is evaluated */
1089 check_release_buf(check, &check->bi);
1090 check_release_buf(check, &check->bo);
1091
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001092 /* No connection, prepare a new one */
Christopher Faulet236c93b2020-07-02 09:19:54 +02001093 cs = cs_new(NULL, (s ? &s->obj_type : &proxy->obj_type));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001094 if (!cs) {
1095 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1096 tcpcheck_get_step_id(check, rule));
1097 if (rule->comment)
1098 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1099 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1100 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001101 TRACE_ERROR("conn-stream allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001102 goto out;
1103 }
1104
Willy Tarreau51cd5952020-06-05 12:25:38 +02001105 tasklet_set_tid(check->wait_list.tasklet, tid);
1106
1107 check->cs = cs;
1108 conn = cs->conn;
1109 conn_set_owner(conn, check->sess, NULL);
1110
1111 /* Maybe there were an older connection we were waiting on */
1112 check->wait_list.events = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001113
1114 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001115 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001116 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001117 status = SF_ERR_RESOURCE;
1118 goto fail_check;
1119 }
1120
1121 /* connect to the connect rule addr if specified, otherwise the check
1122 * addr if specified on the server. otherwise, use the server addr (it
1123 * MUST exist at this step).
1124 */
1125 *conn->dst = (is_addr(&connect->addr)
1126 ? connect->addr
1127 : (is_addr(&check->addr) ? check->addr : s->addr));
Willy Tarreau14e7f292021-10-27 17:41:07 +02001128 proto = protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001129
1130 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001131 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001132 port = connect->port;
1133 if (!port && connect->port_expr) {
1134 struct sample *smp;
1135
1136 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1137 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1138 connect->port_expr, SMP_T_SINT);
1139 if (smp)
1140 port = smp->data.u.sint;
1141 }
1142 if (!port && is_inet_addr(&connect->addr))
1143 port = get_host_port(&connect->addr);
1144 if (!port && check->port)
1145 port = check->port;
1146 if (!port && is_inet_addr(&check->addr))
1147 port = get_host_port(&check->addr);
1148 if (!port) {
1149 /* The server MUST exist here */
1150 port = s->svc_port;
1151 }
1152 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001153 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001154
1155 xprt = ((connect->options & TCPCHK_OPT_SSL)
1156 ? xprt_get(XPRT_SSL)
1157 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1158
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001159 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001160 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001161 status = SF_ERR_RESOURCE;
1162 goto fail_check;
1163 }
1164
Willy Tarreau51cd5952020-06-05 12:25:38 +02001165 cs_attach(cs, check, &check_conn_cb);
1166
Christopher Fauletf7177272020-10-02 13:41:55 +02001167 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1168 conn->send_proxy_ofs = 1;
1169 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001170 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001171 }
1172 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1173 conn->send_proxy_ofs = 1;
1174 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001175 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001176 }
1177
1178 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1179 conn->send_proxy_ofs = 1;
1180 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001181 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001182 }
1183 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1184 conn->send_proxy_ofs = 1;
1185 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001186 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001187 }
1188
Willy Tarreau51cd5952020-06-05 12:25:38 +02001189 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001190 if (proto && proto->connect) {
1191 int flags = 0;
1192
1193 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1194 flags |= CONNECT_HAS_DATA;
1195 if (!next || next->action != TCPCHK_ACT_EXPECT)
1196 flags |= CONNECT_DELACK_ALWAYS;
1197 status = proto->connect(conn, flags);
1198 }
1199
1200 if (status != SF_ERR_NONE)
1201 goto fail_check;
1202
Christopher Faulet21ddc742020-07-01 15:26:14 +02001203 conn_set_private(conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001204 conn->ctx = cs;
1205
Willy Tarreau51cd5952020-06-05 12:25:38 +02001206#ifdef USE_OPENSSL
1207 if (connect->sni)
1208 ssl_sock_set_servername(conn, connect->sni);
1209 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1210 ssl_sock_set_servername(conn, s->check.sni);
1211
1212 if (connect->alpn)
1213 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1214 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1215 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1216#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001217
1218 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1219 /* Some servers don't like reset on close */
Willy Tarreaub41a6e92021-04-06 17:49:19 +02001220 HA_ATOMIC_AND(&fdtab[cs->conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001221 }
1222
1223 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1224 if (xprt_add_hs(conn) < 0)
1225 status = SF_ERR_RESOURCE;
1226 }
1227
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001228 if (conn_xprt_start(conn) < 0) {
1229 status = SF_ERR_RESOURCE;
1230 goto fail_check;
1231 }
1232
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001233 /* The mux may be initialized now if there isn't server attached to the
1234 * check (email alerts) or if there is a mux proto specified or if there
1235 * is no alpn.
1236 */
1237 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1238 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1239 const struct mux_ops *mux_ops;
1240
Christopher Faulet147b8c92021-04-10 09:00:38 +02001241 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001242 if (connect->mux_proto)
1243 mux_ops = connect->mux_proto->mux;
1244 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1245 mux_ops = check->mux_proto->mux;
1246 else {
1247 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1248 ? PROTO_MODE_HTTP
1249 : PROTO_MODE_TCP);
1250
1251 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1252 }
1253 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001254 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001255 status = SF_ERR_INTERNAL;
1256 goto fail_check;
1257 }
1258 }
1259
Willy Tarreau51cd5952020-06-05 12:25:38 +02001260 fail_check:
1261 /* It can return one of :
1262 * - SF_ERR_NONE if everything's OK
1263 * - SF_ERR_SRVTO if there are no more servers
1264 * - SF_ERR_SRVCL if the connection was refused by the server
1265 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1266 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1267 * - SF_ERR_INTERNAL for any other purely internal errors
1268 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1269 * Note that we try to prevent the network stack from sending the ACK during the
1270 * connect() when a pure TCP check is used (without PROXY protocol).
1271 */
1272 switch (status) {
1273 case SF_ERR_NONE:
1274 /* we allow up to min(inter, timeout.connect) for a connection
1275 * to establish but only when timeout.check is set as it may be
1276 * to short for a full check otherwise
1277 */
1278 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1279
1280 if (proxy->timeout.check && proxy->timeout.connect) {
1281 int t_con = tick_add(now_ms, proxy->timeout.connect);
1282 t->expire = tick_first(t->expire, t_con);
1283 }
1284 break;
1285 case SF_ERR_SRVTO: /* ETIMEDOUT */
1286 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1287 case SF_ERR_PRXCOND:
1288 case SF_ERR_RESOURCE:
1289 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001290 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check, 0, 0, (size_t[]){status});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001291 chk_report_conn_err(check, errno, 0);
1292 ret = TCPCHK_EVAL_STOP;
1293 goto out;
1294 }
1295
1296 /* don't do anything until the connection is established */
1297 if (conn->flags & CO_FL_WAIT_XPRT) {
1298 if (conn->mux) {
1299 if (next && next->action == TCPCHK_ACT_SEND)
1300 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1301 else
1302 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
1303 }
1304 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001305 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001306 goto out;
1307 }
1308
1309 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001310 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001311 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001312 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001313 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001314
1315 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1316 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1317
Christopher Faulet147b8c92021-04-10 09:00:38 +02001318 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001319 return ret;
1320}
1321
1322/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1323 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1324 * TCPCHK_EVAL_STOP if an error occurred.
1325 */
1326enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1327{
1328 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1329 struct tcpcheck_send *send = &rule->send;
1330 struct conn_stream *cs = check->cs;
1331 struct connection *conn = cs_conn(cs);
1332 struct buffer *tmp = NULL;
1333 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001334 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001335
Christopher Faulet147b8c92021-04-10 09:00:38 +02001336 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1337
Christopher Fauletb381a502020-11-25 13:47:00 +01001338 if (check->state & CHK_ST_OUT_ALLOC) {
1339 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001340 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001341 goto out;
1342 }
1343
1344 if (!check_get_buf(check, &check->bo)) {
1345 check->state |= CHK_ST_OUT_ALLOC;
1346 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001347 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001348 goto out;
1349 }
1350
Christopher Faulet39066c22020-11-25 13:34:51 +01001351 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001352 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001353 TRACE_DEVEL("Data still pending, try to send it now", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Christopher Faulet39066c22020-11-25 13:34:51 +01001354 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001355 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001356
Christopher Fauletb381a502020-11-25 13:47:00 +01001357 /* Always release input buffer when a new send is evaluated */
1358 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001359
1360 switch (send->type) {
1361 case TCPCHK_SEND_STRING:
1362 case TCPCHK_SEND_BINARY:
1363 if (istlen(send->data) >= b_size(&check->bo)) {
1364 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1365 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1366 tcpcheck_get_step_id(check, rule));
1367 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1368 ret = TCPCHK_EVAL_STOP;
1369 goto out;
1370 }
1371 b_putist(&check->bo, send->data);
1372 break;
1373 case TCPCHK_SEND_STRING_LF:
1374 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1375 if (!b_data(&check->bo))
1376 goto out;
1377 break;
1378 case TCPCHK_SEND_BINARY_LF: {
1379 int len = b_size(&check->bo);
1380
1381 tmp = alloc_trash_chunk();
1382 if (!tmp)
1383 goto error_lf;
1384 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1385 if (!b_data(tmp))
1386 goto out;
1387 tmp->area[tmp->data] = '\0';
1388 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1389 goto error_lf;
1390 check->bo.data = len;
1391 break;
1392 }
1393 case TCPCHK_SEND_HTTP: {
1394 struct htx_sl *sl;
1395 struct ist meth, uri, vsn, clen, body;
1396 unsigned int slflags = 0;
1397
1398 tmp = alloc_trash_chunk();
1399 if (!tmp)
1400 goto error_htx;
1401
1402 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1403 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1404 : http_known_methods[send->http.meth.meth]);
1405 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1406 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1407 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1408 }
1409 else
1410 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1411 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1412
1413 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1414 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1415 slflags |= HTX_SL_F_VER_11;
1416 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1417 if (!isttest(send->http.body))
1418 slflags |= HTX_SL_F_BODYLESS;
1419
1420 htx = htx_from_buf(&check->bo);
1421 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1422 if (!sl)
1423 goto error_htx;
1424 sl->info.req.meth = send->http.meth.meth;
1425 if (!http_update_host(htx, sl, uri))
1426 goto error_htx;
1427
1428 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1429 struct tcpcheck_http_hdr *hdr;
1430 struct ist hdr_value;
1431
1432 list_for_each_entry(hdr, &send->http.hdrs, list) {
1433 chunk_reset(tmp);
1434 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1435 if (!b_data(tmp))
1436 continue;
1437 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1438 if (!htx_add_header(htx, hdr->name, hdr_value))
1439 goto error_htx;
1440 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1441 if (!http_update_authority(htx, sl, hdr_value))
1442 goto error_htx;
1443 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001444 if (isteqi(hdr->name, ist("connection")))
1445 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001446 }
1447
1448 }
1449 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1450 chunk_reset(tmp);
1451 httpchk_build_status_header(check->server, tmp);
1452 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1453 goto error_htx;
1454 }
1455
1456
1457 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1458 chunk_reset(tmp);
1459 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1460 body = ist2(b_orig(tmp), b_data(tmp));
1461 }
1462 else
1463 body = send->http.body;
1464 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1465
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001466 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001467 !htx_add_header(htx, ist("Content-length"), clen))
1468 goto error_htx;
1469
1470
1471 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001472 (istlen(body) && !htx_add_data_atonce(htx, body)))
1473 goto error_htx;
1474
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001475 /* no more data are expected */
1476 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001477 htx_to_buf(htx, &check->bo);
1478 break;
1479 }
1480 case TCPCHK_SEND_UNDEF:
1481 /* Should never happen. */
1482 ret = TCPCHK_EVAL_STOP;
1483 goto out;
1484 };
1485
Christopher Faulet39066c22020-11-25 13:34:51 +01001486 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001487 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001488 if (conn->mux->snd_buf(cs, &check->bo,
1489 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1490 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1491 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001492 TRACE_DEVEL("connection error during send", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001493 goto out;
1494 }
1495 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001496 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001497 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1498 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001499 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001500 goto out;
1501 }
1502
1503 out:
1504 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001505 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1506 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001507
1508 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001509 return ret;
1510
1511 error_htx:
1512 if (htx) {
1513 htx_reset(htx);
1514 htx_to_buf(htx, &check->bo);
1515 }
1516 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1517 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001518 TRACE_ERROR("failed to build HTTP request", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001519 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1520 ret = TCPCHK_EVAL_STOP;
1521 goto out;
1522
1523 error_lf:
1524 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1525 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001526 TRACE_ERROR("failed to build log-format string", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001527 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1528 ret = TCPCHK_EVAL_STOP;
1529 goto out;
1530
1531}
1532
1533/* Try to receive data before evaluating a tcp-check expect rule. Returns
1534 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1535 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1536 * TCPCHK_EVAL_STOP if an error occurred.
1537 */
1538enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1539{
1540 struct conn_stream *cs = check->cs;
1541 struct connection *conn = cs_conn(cs);
1542 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1543 size_t max, read, cur_read = 0;
1544 int is_empty;
1545 int read_poll = MAX_READ_POLL_LOOPS;
1546
Christopher Faulet147b8c92021-04-10 09:00:38 +02001547 TRACE_ENTER(CHK_EV_RX_DATA, check);
1548
1549 if (check->wait_list.events & SUB_RETRY_RECV) {
1550 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001551 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001552 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001553
1554 if (cs->flags & CS_FL_EOS)
1555 goto end_recv;
1556
Christopher Faulet147b8c92021-04-10 09:00:38 +02001557 if (check->state & CHK_ST_IN_ALLOC) {
1558 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001559 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001560 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001561
1562 if (!check_get_buf(check, &check->bi)) {
1563 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001564 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001565 goto wait_more_data;
1566 }
1567
Willy Tarreau51cd5952020-06-05 12:25:38 +02001568 /* errors on the connection and the conn-stream were already checked */
1569
1570 /* prepare to detect if the mux needs more room */
1571 cs->flags &= ~CS_FL_WANT_ROOM;
1572
1573 while ((cs->flags & CS_FL_RCV_MORE) ||
1574 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1575 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1576 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1577 cur_read += read;
1578 if (!read ||
1579 (cs->flags & CS_FL_WANT_ROOM) ||
1580 (--read_poll <= 0) ||
1581 (read < max && read >= global.tune.recv_enough))
1582 break;
1583 }
1584
1585 end_recv:
1586 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1587 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1588 /* Report network errors only if we got no other data. Otherwise
1589 * we'll let the upper layers decide whether the response is OK
1590 * or not. It is very common that an RST sent by the server is
1591 * reported as an error just after the last data chunk.
1592 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001593 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001594 goto stop;
1595 }
1596 if (!cur_read) {
Christopher Fauletd16e7dd2021-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;
Tim Duesterhus4c8f75f2021-11-06 15:14:44 +01001821 value = ist2(istend(value) - istlen(vpat), istlen(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001822 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 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002170 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002171 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
Willy Tarreau1057bee2021-10-06 11:38:44 +02002297 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002298 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
Willy Tarreaud535f802021-10-11 08:49:26 +02002343 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002344 if (!actrule) {
2345 memprintf(errmsg, "out of memory");
2346 goto error;
2347 }
2348 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002349
2350 cur_arg++;
2351 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2352 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2353 goto error;
2354 }
2355
2356 chk = calloc(1, sizeof(*chk));
2357 if (!chk) {
2358 memprintf(errmsg, "out of memory");
2359 goto error;
2360 }
2361 chk->action = TCPCHK_ACT_ACTION_KW;
2362 chk->action_kw.rule = actrule;
2363 return chk;
2364
2365 error:
2366 free(actrule);
2367 return NULL;
2368}
2369
2370/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2371 * returned on error.
2372 */
2373struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2374 const char *file, int line, char **errmsg)
2375{
2376 struct tcpcheck_rule *chk = NULL;
2377 struct sockaddr_storage *sk = NULL;
2378 char *comment = NULL, *sni = NULL, *alpn = NULL;
2379 struct sample_expr *port_expr = NULL;
2380 const struct mux_proto_list *mux_proto = NULL;
2381 unsigned short conn_opts = 0;
2382 long port = 0;
2383 int alpn_len = 0;
2384
2385 list_for_each_entry(chk, rules, list) {
2386 if (chk->action == TCPCHK_ACT_CONNECT)
2387 break;
2388 if (chk->action == TCPCHK_ACT_COMMENT ||
2389 chk->action == TCPCHK_ACT_ACTION_KW ||
2390 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2391 continue;
2392
2393 memprintf(errmsg, "first step MUST also be a 'connect', "
2394 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2395 "when there is a 'connect' step in the tcp-check ruleset");
2396 goto error;
2397 }
2398
2399 cur_arg++;
2400 while (*(args[cur_arg])) {
2401 if (strcmp(args[cur_arg], "default") == 0)
2402 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2403 else if (strcmp(args[cur_arg], "addr") == 0) {
2404 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002405
2406 if (!*(args[cur_arg+1])) {
2407 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2408 goto error;
2409 }
2410
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002411 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2412 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002413 if (!sk) {
2414 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2415 goto error;
2416 }
2417
Willy Tarreau51cd5952020-06-05 12:25:38 +02002418 cur_arg++;
2419 }
2420 else if (strcmp(args[cur_arg], "port") == 0) {
2421 const char *p, *end;
2422
2423 if (!*(args[cur_arg+1])) {
2424 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2425 goto error;
2426 }
2427 cur_arg++;
2428
2429 port = 0;
2430 release_sample_expr(port_expr);
2431 p = args[cur_arg]; end = p + strlen(p);
2432 port = read_uint(&p, end);
2433 if (p != end) {
2434 int idx = 0;
2435
2436 px->conf.args.ctx = ARGC_SRV;
2437 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002438 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002439
2440 if (!port_expr) {
2441 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2442 goto error;
2443 }
2444 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2445 memprintf(errmsg, "error detected while parsing port expression : "
2446 " fetch method '%s' extracts information from '%s', "
2447 "none of which is available here.\n",
2448 args[cur_arg], sample_src_names(port_expr->fetch->use));
2449 goto error;
2450 }
2451 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2452 }
2453 else if (port > 65535 || port < 1) {
2454 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2455 args[cur_arg]);
2456 goto error;
2457 }
2458 }
2459 else if (strcmp(args[cur_arg], "proto") == 0) {
2460 if (!*(args[cur_arg+1])) {
2461 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2462 goto error;
2463 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002464 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002465 if (!mux_proto) {
2466 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2467 goto error;
2468 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002469
2470 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2471 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2472 goto error;
2473 }
2474 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2475 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2476 goto error;
2477 }
2478
Willy Tarreau51cd5952020-06-05 12:25:38 +02002479 cur_arg++;
2480 }
2481 else if (strcmp(args[cur_arg], "comment") == 0) {
2482 if (!*(args[cur_arg+1])) {
2483 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2484 goto error;
2485 }
2486 cur_arg++;
2487 free(comment);
2488 comment = strdup(args[cur_arg]);
2489 if (!comment) {
2490 memprintf(errmsg, "out of memory");
2491 goto error;
2492 }
2493 }
2494 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2495 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2496 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2497 conn_opts |= TCPCHK_OPT_SOCKS4;
2498 else if (strcmp(args[cur_arg], "linger") == 0)
2499 conn_opts |= TCPCHK_OPT_LINGER;
2500#ifdef USE_OPENSSL
2501 else if (strcmp(args[cur_arg], "ssl") == 0) {
2502 px->options |= PR_O_TCPCHK_SSL;
2503 conn_opts |= TCPCHK_OPT_SSL;
2504 }
2505 else if (strcmp(args[cur_arg], "sni") == 0) {
2506 if (!*(args[cur_arg+1])) {
2507 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2508 goto error;
2509 }
2510 cur_arg++;
2511 free(sni);
2512 sni = strdup(args[cur_arg]);
2513 if (!sni) {
2514 memprintf(errmsg, "out of memory");
2515 goto error;
2516 }
2517 }
2518 else if (strcmp(args[cur_arg], "alpn") == 0) {
2519#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2520 free(alpn);
2521 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2522 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2523 goto error;
2524 }
2525 cur_arg++;
2526#else
2527 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2528 goto error;
2529#endif
2530 }
2531#endif /* USE_OPENSSL */
2532
2533 else {
2534 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2535#ifdef USE_OPENSSL
2536 ", 'ssl', 'sni', 'alpn'"
2537#endif /* USE_OPENSSL */
2538 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2539 args[cur_arg]);
2540 goto error;
2541 }
2542 cur_arg++;
2543 }
2544
2545 chk = calloc(1, sizeof(*chk));
2546 if (!chk) {
2547 memprintf(errmsg, "out of memory");
2548 goto error;
2549 }
2550 chk->action = TCPCHK_ACT_CONNECT;
2551 chk->comment = comment;
2552 chk->connect.port = port;
2553 chk->connect.options = conn_opts;
2554 chk->connect.sni = sni;
2555 chk->connect.alpn = alpn;
2556 chk->connect.alpn_len= alpn_len;
2557 chk->connect.port_expr= port_expr;
2558 chk->connect.mux_proto= mux_proto;
2559 if (sk)
2560 chk->connect.addr = *sk;
2561 return chk;
2562
2563 error:
2564 free(alpn);
2565 free(sni);
2566 free(comment);
2567 release_sample_expr(port_expr);
2568 return NULL;
2569}
2570
2571/* Parses and creates a tcp-check send rule. NULL is returned on error */
2572struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2573 const char *file, int line, char **errmsg)
2574{
2575 struct tcpcheck_rule *chk = NULL;
2576 char *comment = NULL, *data = NULL;
2577 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2578
2579 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2580 type = TCPCHK_SEND_BINARY_LF;
2581 else if (strcmp(args[cur_arg], "send-binary") == 0)
2582 type = TCPCHK_SEND_BINARY;
2583 else if (strcmp(args[cur_arg], "send-lf") == 0)
2584 type = TCPCHK_SEND_STRING_LF;
2585 else if (strcmp(args[cur_arg], "send") == 0)
2586 type = TCPCHK_SEND_STRING;
2587
2588 if (!*(args[cur_arg+1])) {
2589 memprintf(errmsg, "'%s' expects a %s as argument",
2590 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2591 goto error;
2592 }
2593
2594 data = args[cur_arg+1];
2595
2596 cur_arg += 2;
2597 while (*(args[cur_arg])) {
2598 if (strcmp(args[cur_arg], "comment") == 0) {
2599 if (!*(args[cur_arg+1])) {
2600 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2601 goto error;
2602 }
2603 cur_arg++;
2604 free(comment);
2605 comment = strdup(args[cur_arg]);
2606 if (!comment) {
2607 memprintf(errmsg, "out of memory");
2608 goto error;
2609 }
2610 }
2611 else {
2612 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2613 args[cur_arg]);
2614 goto error;
2615 }
2616 cur_arg++;
2617 }
2618
2619 chk = calloc(1, sizeof(*chk));
2620 if (!chk) {
2621 memprintf(errmsg, "out of memory");
2622 goto error;
2623 }
2624 chk->action = TCPCHK_ACT_SEND;
2625 chk->comment = comment;
2626 chk->send.type = type;
2627
2628 switch (chk->send.type) {
2629 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002630 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002631 if (!isttest(chk->send.data)) {
2632 memprintf(errmsg, "out of memory");
2633 goto error;
2634 }
2635 break;
2636 case TCPCHK_SEND_BINARY: {
2637 int len = chk->send.data.len;
2638 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2639 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2640 goto error;
2641 }
2642 chk->send.data.len = len;
2643 break;
2644 }
2645 case TCPCHK_SEND_STRING_LF:
2646 case TCPCHK_SEND_BINARY_LF:
2647 LIST_INIT(&chk->send.fmt);
2648 px->conf.args.ctx = ARGC_SRV;
2649 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2650 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2651 goto error;
2652 }
2653 break;
2654 case TCPCHK_SEND_HTTP:
2655 case TCPCHK_SEND_UNDEF:
2656 goto error;
2657 }
2658
2659 return chk;
2660
2661 error:
2662 free(chk);
2663 free(comment);
2664 return NULL;
2665}
2666
2667/* Parses and creates a http-check send rule. NULL is returned on error */
2668struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2669 const char *file, int line, char **errmsg)
2670{
2671 struct tcpcheck_rule *chk = NULL;
2672 struct tcpcheck_http_hdr *hdr = NULL;
2673 struct http_hdr hdrs[global.tune.max_http_hdr];
2674 char *meth = NULL, *uri = NULL, *vsn = NULL;
2675 char *body = NULL, *comment = NULL;
2676 unsigned int flags = 0;
2677 int i = 0, host_hdr = -1;
2678
2679 cur_arg++;
2680 while (*(args[cur_arg])) {
2681 if (strcmp(args[cur_arg], "meth") == 0) {
2682 if (!*(args[cur_arg+1])) {
2683 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2684 goto error;
2685 }
2686 cur_arg++;
2687 meth = args[cur_arg];
2688 }
2689 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2690 if (!*(args[cur_arg+1])) {
2691 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2692 goto error;
2693 }
2694 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2695 if (strcmp(args[cur_arg], "uri-lf") == 0)
2696 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2697 cur_arg++;
2698 uri = args[cur_arg];
2699 }
2700 else if (strcmp(args[cur_arg], "ver") == 0) {
2701 if (!*(args[cur_arg+1])) {
2702 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2703 goto error;
2704 }
2705 cur_arg++;
2706 vsn = args[cur_arg];
2707 }
2708 else if (strcmp(args[cur_arg], "hdr") == 0) {
2709 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2710 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2711 goto error;
2712 }
2713
2714 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2715 if (host_hdr >= 0) {
2716 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2717 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2718 goto error;
2719 }
2720 host_hdr = i;
2721 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002722 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002723 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2724 goto skip_hdr;
2725
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002726 hdrs[i].n = ist(args[cur_arg + 1]);
2727 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002728 i++;
2729 skip_hdr:
2730 cur_arg += 2;
2731 }
2732 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2733 if (!*(args[cur_arg+1])) {
2734 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2735 goto error;
2736 }
2737 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2738 if (strcmp(args[cur_arg], "body-lf") == 0)
2739 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2740 cur_arg++;
2741 body = args[cur_arg];
2742 }
2743 else if (strcmp(args[cur_arg], "comment") == 0) {
2744 if (!*(args[cur_arg+1])) {
2745 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2746 goto error;
2747 }
2748 cur_arg++;
2749 free(comment);
2750 comment = strdup(args[cur_arg]);
2751 if (!comment) {
2752 memprintf(errmsg, "out of memory");
2753 goto error;
2754 }
2755 }
2756 else {
2757 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2758 " but got '%s' as argument.", args[cur_arg]);
2759 goto error;
2760 }
2761 cur_arg++;
2762 }
2763
2764 hdrs[i].n = hdrs[i].v = IST_NULL;
2765
2766 chk = calloc(1, sizeof(*chk));
2767 if (!chk) {
2768 memprintf(errmsg, "out of memory");
2769 goto error;
2770 }
2771 chk->action = TCPCHK_ACT_SEND;
2772 chk->comment = comment; comment = NULL;
2773 chk->send.type = TCPCHK_SEND_HTTP;
2774 chk->send.http.flags = flags;
2775 LIST_INIT(&chk->send.http.hdrs);
2776
2777 if (meth) {
2778 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2779 chk->send.http.meth.str.area = strdup(meth);
2780 chk->send.http.meth.str.data = strlen(meth);
2781 if (!chk->send.http.meth.str.area) {
2782 memprintf(errmsg, "out of memory");
2783 goto error;
2784 }
2785 }
2786 if (uri) {
2787 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2788 LIST_INIT(&chk->send.http.uri_fmt);
2789 px->conf.args.ctx = ARGC_SRV;
2790 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2791 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2792 goto error;
2793 }
2794 }
2795 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002796 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002797 if (!isttest(chk->send.http.uri)) {
2798 memprintf(errmsg, "out of memory");
2799 goto error;
2800 }
2801 }
2802 }
2803 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002804 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002805 if (!isttest(chk->send.http.vsn)) {
2806 memprintf(errmsg, "out of memory");
2807 goto error;
2808 }
2809 }
2810 for (i = 0; istlen(hdrs[i].n); i++) {
2811 hdr = calloc(1, sizeof(*hdr));
2812 if (!hdr) {
2813 memprintf(errmsg, "out of memory");
2814 goto error;
2815 }
2816 LIST_INIT(&hdr->value);
2817 hdr->name = istdup(hdrs[i].n);
2818 if (!isttest(hdr->name)) {
2819 memprintf(errmsg, "out of memory");
2820 goto error;
2821 }
2822
2823 ist0(hdrs[i].v);
2824 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2825 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002826 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002827 hdr = NULL;
2828 }
2829
2830 if (body) {
2831 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2832 LIST_INIT(&chk->send.http.body_fmt);
2833 px->conf.args.ctx = ARGC_SRV;
2834 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2835 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2836 goto error;
2837 }
2838 }
2839 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002840 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002841 if (!isttest(chk->send.http.body)) {
2842 memprintf(errmsg, "out of memory");
2843 goto error;
2844 }
2845 }
2846 }
2847
2848 return chk;
2849
2850 error:
2851 free_tcpcheck_http_hdr(hdr);
2852 free_tcpcheck(chk, 0);
2853 free(comment);
2854 return NULL;
2855}
2856
2857/* Parses and creates a http-check comment rule. NULL is returned on error */
2858struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2859 const char *file, int line, char **errmsg)
2860{
2861 struct tcpcheck_rule *chk = NULL;
2862 char *comment = NULL;
2863
2864 if (!*(args[cur_arg+1])) {
2865 memprintf(errmsg, "expects a string as argument");
2866 goto error;
2867 }
2868 cur_arg++;
2869 comment = strdup(args[cur_arg]);
2870 if (!comment) {
2871 memprintf(errmsg, "out of memory");
2872 goto error;
2873 }
2874
2875 chk = calloc(1, sizeof(*chk));
2876 if (!chk) {
2877 memprintf(errmsg, "out of memory");
2878 goto error;
2879 }
2880 chk->action = TCPCHK_ACT_COMMENT;
2881 chk->comment = comment;
2882 return chk;
2883
2884 error:
2885 free(comment);
2886 return NULL;
2887}
2888
2889/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2890 * on error. <proto> is set to the right protocol flags (covered by the
2891 * TCPCHK_RULES_PROTO_CHK mask).
2892 */
2893struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2894 struct list *rules, unsigned int proto,
2895 const char *file, int line, char **errmsg)
2896{
2897 struct tcpcheck_rule *prev_check, *chk = NULL;
2898 struct sample_expr *status_expr = NULL;
2899 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2900 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2901 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2902 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2903 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2904 unsigned int flags = 0;
2905 long min_recv = -1;
2906 int inverse = 0;
2907
2908 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2909 if (!*(args[cur_arg+1])) {
2910 memprintf(errmsg, "expects at least a matching pattern as arguments");
2911 goto error;
2912 }
2913
2914 cur_arg++;
2915 while (*(args[cur_arg])) {
2916 int in_pattern = 0;
2917
2918 rescan:
2919 if (strcmp(args[cur_arg], "min-recv") == 0) {
2920 if (in_pattern) {
2921 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2922 goto error;
2923 }
2924 if (!*(args[cur_arg+1])) {
2925 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2926 goto error;
2927 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002928 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002929 cur_arg++;
2930 min_recv = atol(args[cur_arg]);
2931 if (min_recv < -1 || min_recv > INT_MAX) {
2932 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2933 goto error;
2934 }
2935 }
2936 else if (*(args[cur_arg]) == '!') {
2937 in_pattern = 1;
2938 while (*(args[cur_arg]) == '!') {
2939 inverse = !inverse;
2940 args[cur_arg]++;
2941 }
2942 if (!*(args[cur_arg]))
2943 cur_arg++;
2944 goto rescan;
2945 }
2946 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2947 if (type != TCPCHK_EXPECT_UNDEF) {
2948 memprintf(errmsg, "only on pattern expected");
2949 goto error;
2950 }
2951 if (proto != TCPCHK_RULES_HTTP_CHK)
2952 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2953 else
2954 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2955
2956 if (!*(args[cur_arg+1])) {
2957 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2958 goto error;
2959 }
2960 cur_arg++;
2961 pattern = args[cur_arg];
2962 }
2963 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2964 if (proto == TCPCHK_RULES_HTTP_CHK)
2965 goto bad_http_kw;
2966 if (type != TCPCHK_EXPECT_UNDEF) {
2967 memprintf(errmsg, "only on pattern expected");
2968 goto error;
2969 }
2970 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2971
2972 if (!*(args[cur_arg+1])) {
2973 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2974 goto error;
2975 }
2976 cur_arg++;
2977 pattern = args[cur_arg];
2978 }
2979 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2980 if (type != TCPCHK_EXPECT_UNDEF) {
2981 memprintf(errmsg, "only on pattern expected");
2982 goto error;
2983 }
2984 if (proto != TCPCHK_RULES_HTTP_CHK)
2985 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2986 else {
2987 if (*(args[cur_arg]) != 's')
2988 goto bad_http_kw;
2989 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2990 }
2991
2992 if (!*(args[cur_arg+1])) {
2993 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2994 goto error;
2995 }
2996 cur_arg++;
2997 pattern = args[cur_arg];
2998 }
2999 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3000 if (proto != TCPCHK_RULES_HTTP_CHK)
3001 goto bad_tcp_kw;
3002 if (type != TCPCHK_EXPECT_UNDEF) {
3003 memprintf(errmsg, "only on pattern expected");
3004 goto error;
3005 }
3006 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3007
3008 if (!*(args[cur_arg+1])) {
3009 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3010 goto error;
3011 }
3012 cur_arg++;
3013 pattern = args[cur_arg];
3014 }
3015 else if (strcmp(args[cur_arg], "custom") == 0) {
3016 if (in_pattern) {
3017 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3018 goto error;
3019 }
3020 if (type != TCPCHK_EXPECT_UNDEF) {
3021 memprintf(errmsg, "only on pattern expected");
3022 goto error;
3023 }
3024 type = TCPCHK_EXPECT_CUSTOM;
3025 }
3026 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3027 int orig_arg = cur_arg;
3028
3029 if (proto != TCPCHK_RULES_HTTP_CHK)
3030 goto bad_tcp_kw;
3031 if (type != TCPCHK_EXPECT_UNDEF) {
3032 memprintf(errmsg, "only on pattern expected");
3033 goto error;
3034 }
3035 type = TCPCHK_EXPECT_HTTP_HEADER;
3036
3037 if (strcmp(args[cur_arg], "fhdr") == 0)
3038 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3039
3040 /* Parse the name pattern, mandatory */
3041 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3042 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3043 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3044 args[orig_arg]);
3045 goto error;
3046 }
3047
3048 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3049 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3050
3051 cur_arg += 2;
3052 if (strcmp(args[cur_arg], "-m") == 0) {
3053 if (!*(args[cur_arg+1])) {
3054 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3055 args[orig_arg], args[cur_arg]);
3056 goto error;
3057 }
3058 if (strcmp(args[cur_arg+1], "str") == 0)
3059 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3060 else if (strcmp(args[cur_arg+1], "beg") == 0)
3061 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3062 else if (strcmp(args[cur_arg+1], "end") == 0)
3063 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3064 else if (strcmp(args[cur_arg+1], "sub") == 0)
3065 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3066 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3067 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3068 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3069 args[orig_arg]);
3070 goto error;
3071 }
3072 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3073 }
3074 else {
3075 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3076 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3077 goto error;
3078 }
3079 cur_arg += 2;
3080 }
3081 else
3082 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3083 npat = args[cur_arg];
3084
3085 if (!*(args[cur_arg+1]) ||
3086 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3087 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3088 goto next;
3089 }
3090 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3091 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3092
3093 /* Parse the value pattern, optional */
3094 if (strcmp(args[cur_arg+2], "-m") == 0) {
3095 cur_arg += 2;
3096 if (!*(args[cur_arg+1])) {
3097 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3098 args[orig_arg], args[cur_arg]);
3099 goto error;
3100 }
3101 if (strcmp(args[cur_arg+1], "str") == 0)
3102 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3103 else if (strcmp(args[cur_arg+1], "beg") == 0)
3104 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3105 else if (strcmp(args[cur_arg+1], "end") == 0)
3106 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3107 else if (strcmp(args[cur_arg+1], "sub") == 0)
3108 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3109 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3110 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3111 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3112 args[orig_arg]);
3113 goto error;
3114 }
3115 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3116 }
3117 else {
3118 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3119 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3120 goto error;
3121 }
3122 }
3123 else
3124 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3125
3126 if (!*(args[cur_arg+2])) {
3127 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3128 goto error;
3129 }
3130 vpat = args[cur_arg+2];
3131 cur_arg += 2;
3132 }
3133 else if (strcmp(args[cur_arg], "comment") == 0) {
3134 if (in_pattern) {
3135 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3136 goto error;
3137 }
3138 if (!*(args[cur_arg+1])) {
3139 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3140 goto error;
3141 }
3142 cur_arg++;
3143 free(comment);
3144 comment = strdup(args[cur_arg]);
3145 if (!comment) {
3146 memprintf(errmsg, "out of memory");
3147 goto error;
3148 }
3149 }
3150 else if (strcmp(args[cur_arg], "on-success") == 0) {
3151 if (in_pattern) {
3152 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3153 goto error;
3154 }
3155 if (!*(args[cur_arg+1])) {
3156 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3157 goto error;
3158 }
3159 cur_arg++;
3160 on_success_msg = args[cur_arg];
3161 }
3162 else if (strcmp(args[cur_arg], "on-error") == 0) {
3163 if (in_pattern) {
3164 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3165 goto error;
3166 }
3167 if (!*(args[cur_arg+1])) {
3168 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3169 goto error;
3170 }
3171 cur_arg++;
3172 on_error_msg = args[cur_arg];
3173 }
3174 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3175 if (in_pattern) {
3176 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3177 goto error;
3178 }
3179 if (!*(args[cur_arg+1])) {
3180 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3181 goto error;
3182 }
3183 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3184 ok_st = HCHK_STATUS_L7OKD;
3185 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3186 ok_st = HCHK_STATUS_L7OKCD;
3187 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3188 ok_st = HCHK_STATUS_L6OK;
3189 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3190 ok_st = HCHK_STATUS_L4OK;
3191 else {
3192 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3193 args[cur_arg], args[cur_arg+1]);
3194 goto error;
3195 }
3196 cur_arg++;
3197 }
3198 else if (strcmp(args[cur_arg], "error-status") == 0) {
3199 if (in_pattern) {
3200 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3201 goto error;
3202 }
3203 if (!*(args[cur_arg+1])) {
3204 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3205 goto error;
3206 }
3207 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3208 err_st = HCHK_STATUS_L7RSP;
3209 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3210 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003211 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3212 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003213 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3214 err_st = HCHK_STATUS_L6RSP;
3215 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3216 err_st = HCHK_STATUS_L4CON;
3217 else {
3218 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3219 args[cur_arg], args[cur_arg+1]);
3220 goto error;
3221 }
3222 cur_arg++;
3223 }
3224 else if (strcmp(args[cur_arg], "status-code") == 0) {
3225 int idx = 0;
3226
3227 if (in_pattern) {
3228 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3229 goto error;
3230 }
3231 if (!*(args[cur_arg+1])) {
3232 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3233 goto error;
3234 }
3235
3236 cur_arg++;
3237 release_sample_expr(status_expr);
3238 px->conf.args.ctx = ARGC_SRV;
3239 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003240 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003241 if (!status_expr) {
3242 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3243 goto error;
3244 }
3245 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3246 memprintf(errmsg, "error detected while parsing status-code expression : "
3247 " fetch method '%s' extracts information from '%s', "
3248 "none of which is available here.\n",
3249 args[cur_arg], sample_src_names(status_expr->fetch->use));
3250 goto error;
3251 }
3252 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3253 }
3254 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3255 if (in_pattern) {
3256 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3257 goto error;
3258 }
3259 if (!*(args[cur_arg+1])) {
3260 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3261 goto error;
3262 }
3263 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3264 tout_st = HCHK_STATUS_L7TOUT;
3265 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3266 tout_st = HCHK_STATUS_L6TOUT;
3267 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3268 tout_st = HCHK_STATUS_L4TOUT;
3269 else {
3270 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3271 args[cur_arg], args[cur_arg+1]);
3272 goto error;
3273 }
3274 cur_arg++;
3275 }
3276 else {
3277 if (proto == TCPCHK_RULES_HTTP_CHK) {
3278 bad_http_kw:
3279 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3280 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3281 }
3282 else {
3283 bad_tcp_kw:
3284 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3285 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3286 }
3287 goto error;
3288 }
3289 next:
3290 cur_arg++;
3291 }
3292
3293 chk = calloc(1, sizeof(*chk));
3294 if (!chk) {
3295 memprintf(errmsg, "out of memory");
3296 goto error;
3297 }
3298 chk->action = TCPCHK_ACT_EXPECT;
3299 LIST_INIT(&chk->expect.onerror_fmt);
3300 LIST_INIT(&chk->expect.onsuccess_fmt);
3301 chk->comment = comment; comment = NULL;
3302 chk->expect.type = type;
3303 chk->expect.min_recv = min_recv;
3304 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3305 chk->expect.ok_status = ok_st;
3306 chk->expect.err_status = err_st;
3307 chk->expect.tout_status = tout_st;
3308 chk->expect.status_expr = status_expr; status_expr = NULL;
3309
3310 if (on_success_msg) {
3311 px->conf.args.ctx = ARGC_SRV;
3312 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3313 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3314 goto error;
3315 }
3316 }
3317 if (on_error_msg) {
3318 px->conf.args.ctx = ARGC_SRV;
3319 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3320 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3321 goto error;
3322 }
3323 }
3324
3325 switch (chk->expect.type) {
3326 case TCPCHK_EXPECT_HTTP_STATUS: {
3327 const char *p = pattern;
3328 unsigned int c1,c2;
3329
3330 chk->expect.codes.codes = NULL;
3331 chk->expect.codes.num = 0;
3332 while (1) {
3333 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3334 if (*p == '-') {
3335 p++;
3336 c2 = read_uint(&p, pattern + strlen(pattern));
3337 }
3338 if (c1 > c2) {
3339 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3340 goto error;
3341 }
3342
3343 chk->expect.codes.num++;
3344 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3345 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3346 if (!chk->expect.codes.codes) {
3347 memprintf(errmsg, "out of memory");
3348 goto error;
3349 }
3350 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3351 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3352
3353 if (*p == '\0')
3354 break;
3355 if (*p != ',') {
3356 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3357 goto error;
3358 }
3359 p++;
3360 }
3361 break;
3362 }
3363 case TCPCHK_EXPECT_STRING:
3364 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003365 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003366 if (!isttest(chk->expect.data)) {
3367 memprintf(errmsg, "out of memory");
3368 goto error;
3369 }
3370 break;
3371 case TCPCHK_EXPECT_BINARY: {
3372 int len = chk->expect.data.len;
3373
3374 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3375 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3376 goto error;
3377 }
3378 chk->expect.data.len = len;
3379 break;
3380 }
3381 case TCPCHK_EXPECT_STRING_REGEX:
3382 case TCPCHK_EXPECT_BINARY_REGEX:
3383 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3384 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3385 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3386 if (!chk->expect.regex)
3387 goto error;
3388 break;
3389
3390 case TCPCHK_EXPECT_STRING_LF:
3391 case TCPCHK_EXPECT_BINARY_LF:
3392 case TCPCHK_EXPECT_HTTP_BODY_LF:
3393 LIST_INIT(&chk->expect.fmt);
3394 px->conf.args.ctx = ARGC_SRV;
3395 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3396 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3397 goto error;
3398 }
3399 break;
3400
3401 case TCPCHK_EXPECT_HTTP_HEADER:
3402 if (!npat) {
3403 memprintf(errmsg, "unexpected error, undefined header name pattern");
3404 goto error;
3405 }
3406 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3407 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3408 if (!chk->expect.hdr.name_re)
3409 goto error;
3410 }
3411 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3412 px->conf.args.ctx = ARGC_SRV;
3413 LIST_INIT(&chk->expect.hdr.name_fmt);
3414 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3415 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3416 goto error;
3417 }
3418 }
3419 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003420 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003421 if (!isttest(chk->expect.hdr.name)) {
3422 memprintf(errmsg, "out of memory");
3423 goto error;
3424 }
3425 }
3426
3427 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3428 chk->expect.hdr.value = IST_NULL;
3429 break;
3430 }
3431
3432 if (!vpat) {
3433 memprintf(errmsg, "unexpected error, undefined header value pattern");
3434 goto error;
3435 }
3436 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3437 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3438 if (!chk->expect.hdr.value_re)
3439 goto error;
3440 }
3441 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3442 px->conf.args.ctx = ARGC_SRV;
3443 LIST_INIT(&chk->expect.hdr.value_fmt);
3444 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3445 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3446 goto error;
3447 }
3448 }
3449 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003450 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003451 if (!isttest(chk->expect.hdr.value)) {
3452 memprintf(errmsg, "out of memory");
3453 goto error;
3454 }
3455 }
3456
3457 break;
3458 case TCPCHK_EXPECT_CUSTOM:
3459 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3460 break;
3461 case TCPCHK_EXPECT_UNDEF:
3462 memprintf(errmsg, "pattern not found");
3463 goto error;
3464 }
3465
3466 /* All tcp-check expect points back to the first inverse expect rule in
3467 * a chain of one or more expect rule, potentially itself.
3468 */
3469 chk->expect.head = chk;
3470 list_for_each_entry_rev(prev_check, rules, list) {
3471 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3472 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3473 chk->expect.head = prev_check;
3474 continue;
3475 }
3476 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3477 break;
3478 }
3479 return chk;
3480
3481 error:
3482 free_tcpcheck(chk, 0);
3483 free(comment);
3484 release_sample_expr(status_expr);
3485 return NULL;
3486}
3487
3488/* Overwrites fields of the old http send rule with those of the new one. When
3489 * replaced, old values are freed and replaced by the new ones. New values are
3490 * not copied but transferred. At the end <new> should be empty and can be
3491 * safely released. This function never fails.
3492 */
3493void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3494{
3495 struct logformat_node *lf, *lfb;
3496 struct tcpcheck_http_hdr *hdr, *bhdr;
3497
3498
3499 if (new->send.http.meth.str.area) {
3500 free(old->send.http.meth.str.area);
3501 old->send.http.meth.meth = new->send.http.meth.meth;
3502 old->send.http.meth.str.area = new->send.http.meth.str.area;
3503 old->send.http.meth.str.data = new->send.http.meth.str.data;
3504 new->send.http.meth.str = BUF_NULL;
3505 }
3506
3507 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3508 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3509 istfree(&old->send.http.uri);
3510 else
3511 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3512 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3513 old->send.http.uri = new->send.http.uri;
3514 new->send.http.uri = IST_NULL;
3515 }
3516 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3517 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3518 istfree(&old->send.http.uri);
3519 else
3520 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3521 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3522 LIST_INIT(&old->send.http.uri_fmt);
3523 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003524 LIST_DELETE(&lf->list);
3525 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003526 }
3527 }
3528
3529 if (isttest(new->send.http.vsn)) {
3530 istfree(&old->send.http.vsn);
3531 old->send.http.vsn = new->send.http.vsn;
3532 new->send.http.vsn = IST_NULL;
3533 }
3534
3535 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3536 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003537 LIST_DELETE(&hdr->list);
3538 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003539 }
3540
3541 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3542 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3543 istfree(&old->send.http.body);
3544 else
3545 free_tcpcheck_fmt(&old->send.http.body_fmt);
3546 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3547 old->send.http.body = new->send.http.body;
3548 new->send.http.body = IST_NULL;
3549 }
3550 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3551 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3552 istfree(&old->send.http.body);
3553 else
3554 free_tcpcheck_fmt(&old->send.http.body_fmt);
3555 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3556 LIST_INIT(&old->send.http.body_fmt);
3557 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003558 LIST_DELETE(&lf->list);
3559 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003560 }
3561 }
3562}
3563
3564/* Internal function used to add an http-check rule in a list during the config
3565 * parsing step. Depending on its type, and the previously inserted rules, a
3566 * specific action may be performed or an error may be reported. This functions
3567 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3568 * message.
3569 */
3570int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3571{
3572 struct tcpcheck_rule *r;
3573
3574 /* the implicit send rule coming from an "option httpchk" line must be
3575 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003576 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003577 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003578 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003579 * sure the ruleset remains valid.
3580 */
3581
3582 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3583 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3584 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3585 * following tests are performed :
3586 *
3587 * 1- If there is no such rule or if it is not a send rule, the implicit send
3588 * rule is pushed in front of the ruleset
3589 *
3590 * 2- If it is another implicit send rule, it is replaced with the new one.
3591 *
3592 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3593 * both, overwriting the old send rule (the explicit one) with info of the
3594 * new send rule (the implicit one).
3595 */
3596 r = get_first_tcpcheck_rule(rules);
3597 if (r && r->action == TCPCHK_ACT_CONNECT)
3598 r = get_next_tcpcheck_rule(rules, r);
3599 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003600 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003601 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003602 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003603 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003604 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003605 }
3606 else {
3607 tcpcheck_overwrite_send_http_rule(r, chk);
3608 free_tcpcheck(chk, 0);
3609 }
3610 }
3611 else {
3612 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3613 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3614 * with an existing implicit send rule, if any. At the end, if there is no error,
3615 * the rule is appended to the list.
3616 */
3617
3618 r = get_last_tcpcheck_rule(rules);
3619 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3620 /* no error */;
3621 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3622 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3623 chk->index+1);
3624 return 0;
3625 }
3626 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3627 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3628 chk->index+1);
3629 return 0;
3630 }
3631 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3632 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3633 chk->index+1);
3634 return 0;
3635 }
3636
3637 if (chk->action == TCPCHK_ACT_SEND) {
3638 r = get_first_tcpcheck_rule(rules);
3639 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3640 tcpcheck_overwrite_send_http_rule(r, chk);
3641 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003642 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003643 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3644 chk = r;
3645 }
3646 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003647 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003648 }
3649 return 1;
3650}
3651
3652/* Check tcp-check health-check configuration for the proxy <px>. */
3653static int check_proxy_tcpcheck(struct proxy *px)
3654{
3655 struct tcpcheck_rule *chk, *back;
3656 char *comment = NULL, *errmsg = NULL;
3657 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003658 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003659
3660 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3661 deinit_proxy_tcpcheck(px);
3662 goto out;
3663 }
3664
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003665 ha_free(&px->check_command);
3666 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003667
3668 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003669 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003670 ret |= ERR_ALERT | ERR_FATAL;
3671 goto out;
3672 }
3673
3674 /* HTTP ruleset only : */
3675 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3676 struct tcpcheck_rule *next;
3677
3678 /* move remaining implicit send rule from "option httpchk" line to the right place.
3679 * If such rule exists, it must be the first one. In this case, the rule is moved
3680 * after the first connect rule, if any. Otherwise, nothing is done.
3681 */
3682 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3683 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3684 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3685 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003686 LIST_DELETE(&chk->list);
3687 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003688 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003689 }
3690 }
3691
3692 /* add implicit expect rule if the last one is a send. It is inherited from previous
3693 * versions where the http expect rule was optional. Now it is possible to chained
3694 * send/expect rules but the last expect may still be implicit.
3695 */
3696 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3697 if (chk && chk->action == TCPCHK_ACT_SEND) {
3698 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3699 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3700 px->conf.file, px->conf.line, &errmsg);
3701 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003702 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003703 "(%s).\n", px->id, errmsg);
3704 free(errmsg);
3705 ret |= ERR_ALERT | ERR_FATAL;
3706 goto out;
3707 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003708 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003709 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003710 }
3711 }
3712
3713 /* For all ruleset: */
3714
3715 /* If there is no connect rule preceding all send / expect rules, an
3716 * implicit one is inserted before all others.
3717 */
3718 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3719 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3720 chk = calloc(1, sizeof(*chk));
3721 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003722 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003723 "(out of memory).\n", px->id);
3724 ret |= ERR_ALERT | ERR_FATAL;
3725 goto out;
3726 }
3727 chk->action = TCPCHK_ACT_CONNECT;
3728 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003729 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003730 }
3731
3732 /* Remove all comment rules. To do so, when a such rule is found, the
3733 * comment is assigned to the following rule(s).
3734 */
3735 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003736 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3737 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003738
3739 prev_action = chk->action;
3740 switch (chk->action) {
3741 case TCPCHK_ACT_COMMENT:
3742 free(comment);
3743 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003744 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003745 free(chk);
3746 break;
3747 case TCPCHK_ACT_CONNECT:
3748 if (!chk->comment && comment)
3749 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003750 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003751 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003752 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003753 break;
3754 case TCPCHK_ACT_SEND:
3755 case TCPCHK_ACT_EXPECT:
3756 if (!chk->comment && comment)
3757 chk->comment = strdup(comment);
3758 break;
3759 }
3760 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003761 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003762
3763 out:
3764 return ret;
3765}
3766
3767void deinit_proxy_tcpcheck(struct proxy *px)
3768{
3769 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3770 px->tcpcheck_rules.flags = 0;
3771 px->tcpcheck_rules.list = NULL;
3772}
3773
3774static void deinit_tcpchecks()
3775{
3776 struct tcpcheck_ruleset *rs;
3777 struct tcpcheck_rule *r, *rb;
3778 struct ebpt_node *node, *next;
3779
3780 node = ebpt_first(&shared_tcpchecks);
3781 while (node) {
3782 next = ebpt_next(node);
3783 ebpt_delete(node);
3784 free(node->key);
3785 rs = container_of(node, typeof(*rs), node);
3786 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003787 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003788 free_tcpcheck(r, 0);
3789 }
3790 free(rs);
3791 node = next;
3792 }
3793}
3794
3795int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3796{
3797 struct tcpcheck_rule *tcpcheck, *prev_check;
3798 struct tcpcheck_expect *expect;
3799
Willy Tarreau6922e552021-03-22 21:11:45 +01003800 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003801 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003802 tcpcheck->action = TCPCHK_ACT_EXPECT;
3803
3804 expect = &tcpcheck->expect;
3805 expect->type = TCPCHK_EXPECT_STRING;
3806 LIST_INIT(&expect->onerror_fmt);
3807 LIST_INIT(&expect->onsuccess_fmt);
3808 expect->ok_status = HCHK_STATUS_L7OKD;
3809 expect->err_status = HCHK_STATUS_L7RSP;
3810 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003811 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003812 if (!isttest(expect->data)) {
3813 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3814 return 0;
3815 }
3816
3817 /* All tcp-check expect points back to the first inverse expect rule
3818 * in a chain of one or more expect rule, potentially itself.
3819 */
3820 tcpcheck->expect.head = tcpcheck;
3821 list_for_each_entry_rev(prev_check, rules->list, list) {
3822 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3823 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3824 tcpcheck->expect.head = prev_check;
3825 continue;
3826 }
3827 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3828 break;
3829 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003830 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003831 return 1;
3832}
3833
3834int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3835{
3836 struct tcpcheck_rule *tcpcheck;
3837 struct tcpcheck_send *send;
3838 const char *in;
3839 char *dst;
3840 int i;
3841
Willy Tarreau6922e552021-03-22 21:11:45 +01003842 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003843 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003844 tcpcheck->action = TCPCHK_ACT_SEND;
3845
3846 send = &tcpcheck->send;
3847 send->type = TCPCHK_SEND_STRING;
3848
3849 for (i = 0; strs[i]; i++)
3850 send->data.len += strlen(strs[i]);
3851
3852 send->data.ptr = malloc(istlen(send->data) + 1);
3853 if (!isttest(send->data)) {
3854 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3855 return 0;
3856 }
3857
3858 dst = istptr(send->data);
3859 for (i = 0; strs[i]; i++)
3860 for (in = strs[i]; (*dst = *in++); dst++);
3861 *dst = 0;
3862
Willy Tarreau2b718102021-04-21 07:32:39 +02003863 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003864 return 1;
3865}
3866
3867/* Parses the "tcp-check" proxy keyword */
3868static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003869 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003870 char **errmsg)
3871{
3872 struct tcpcheck_ruleset *rs = NULL;
3873 struct tcpcheck_rule *chk = NULL;
3874 int index, cur_arg, ret = 0;
3875
3876 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3877 ret = 1;
3878
3879 /* Deduce the ruleset name from the proxy info */
3880 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3881 ((curpx == defpx) ? "defaults" : curpx->id),
3882 curpx->conf.file, curpx->conf.line);
3883
3884 rs = find_tcpcheck_ruleset(b_orig(&trash));
3885 if (rs == NULL) {
3886 rs = create_tcpcheck_ruleset(b_orig(&trash));
3887 if (rs == NULL) {
3888 memprintf(errmsg, "out of memory.\n");
3889 goto error;
3890 }
3891 }
3892
3893 index = 0;
3894 if (!LIST_ISEMPTY(&rs->rules)) {
3895 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3896 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003897 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003898 }
3899
3900 cur_arg = 1;
3901 if (strcmp(args[cur_arg], "connect") == 0)
3902 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3903 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3904 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3905 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3906 else if (strcmp(args[cur_arg], "expect") == 0)
3907 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3908 else if (strcmp(args[cur_arg], "comment") == 0)
3909 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3910 else {
3911 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3912
3913 if (!kw) {
3914 action_kw_tcp_check_build_list(&trash);
3915 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3916 "%s%s. but got '%s'",
3917 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3918 goto error;
3919 }
3920 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3921 }
3922
3923 if (!chk) {
3924 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3925 goto error;
3926 }
3927 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3928
3929 /* No error: add the tcp-check rule in the list */
3930 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003931 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003932
3933 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3934 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3935 /* Use this ruleset if the proxy already has tcp-check enabled */
3936 curpx->tcpcheck_rules.list = &rs->rules;
3937 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3938 }
3939 else {
3940 /* mark this ruleset as unused for now */
3941 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3942 }
3943
3944 return ret;
3945
3946 error:
3947 free_tcpcheck(chk, 0);
3948 free_tcpcheck_ruleset(rs);
3949 return -1;
3950}
3951
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003952/* Parses the "http-check" proxy keyword */
3953static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003954 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003955 char **errmsg)
3956{
3957 struct tcpcheck_ruleset *rs = NULL;
3958 struct tcpcheck_rule *chk = NULL;
3959 int index, cur_arg, ret = 0;
3960
3961 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3962 ret = 1;
3963
3964 cur_arg = 1;
3965 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3966 /* enable a graceful server shutdown on an HTTP 404 response */
3967 curpx->options |= PR_O_DISABLE404;
3968 if (too_many_args(1, args, errmsg, NULL))
3969 goto error;
3970 goto out;
3971 }
3972 else if (strcmp(args[cur_arg], "send-state") == 0) {
3973 /* enable emission of the apparent state of a server in HTTP checks */
3974 curpx->options2 |= PR_O2_CHK_SNDST;
3975 if (too_many_args(1, args, errmsg, NULL))
3976 goto error;
3977 goto out;
3978 }
3979
3980 /* Deduce the ruleset name from the proxy info */
3981 chunk_printf(&trash, "*http-check-%s_%s-%d",
3982 ((curpx == defpx) ? "defaults" : curpx->id),
3983 curpx->conf.file, curpx->conf.line);
3984
3985 rs = find_tcpcheck_ruleset(b_orig(&trash));
3986 if (rs == NULL) {
3987 rs = create_tcpcheck_ruleset(b_orig(&trash));
3988 if (rs == NULL) {
3989 memprintf(errmsg, "out of memory.\n");
3990 goto error;
3991 }
3992 }
3993
3994 index = 0;
3995 if (!LIST_ISEMPTY(&rs->rules)) {
3996 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3997 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
3998 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003999 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004000 }
4001
4002 if (strcmp(args[cur_arg], "connect") == 0)
4003 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4004 else if (strcmp(args[cur_arg], "send") == 0)
4005 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4006 else if (strcmp(args[cur_arg], "expect") == 0)
4007 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4008 file, line, errmsg);
4009 else if (strcmp(args[cur_arg], "comment") == 0)
4010 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4011 else {
4012 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4013
4014 if (!kw) {
4015 action_kw_tcp_check_build_list(&trash);
4016 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4017 " 'send', 'expect'%s%s. but got '%s'",
4018 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4019 goto error;
4020 }
4021 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4022 }
4023
4024 if (!chk) {
4025 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4026 goto error;
4027 }
4028 ret = (*errmsg != NULL); /* Handle warning */
4029
4030 chk->index = index;
4031 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4032 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4033 /* Use this ruleset if the proxy already has http-check enabled */
4034 curpx->tcpcheck_rules.list = &rs->rules;
4035 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4036 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4037 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4038 curpx->tcpcheck_rules.list = NULL;
4039 goto error;
4040 }
4041 }
4042 else {
4043 /* mark this ruleset as unused for now */
4044 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004045 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004046 }
4047
4048 out:
4049 return ret;
4050
4051 error:
4052 free_tcpcheck(chk, 0);
4053 free_tcpcheck_ruleset(rs);
4054 return -1;
4055}
4056
4057/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004058int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004059 const char *file, int line)
4060{
4061 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4062 static char *redis_res = "+PONG\r\n";
4063
4064 struct tcpcheck_ruleset *rs = NULL;
4065 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4066 struct tcpcheck_rule *chk;
4067 char *errmsg = NULL;
4068 int err_code = 0;
4069
4070 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4071 err_code |= ERR_WARN;
4072
4073 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4074 goto out;
4075
4076 curpx->options2 &= ~PR_O2_CHK_ANY;
4077 curpx->options2 |= PR_O2_TCPCHK_CHK;
4078
4079 free_tcpcheck_vars(&rules->preset_vars);
4080 rules->list = NULL;
4081 rules->flags = 0;
4082
4083 rs = find_tcpcheck_ruleset("*redis-check");
4084 if (rs)
4085 goto ruleset_found;
4086
4087 rs = create_tcpcheck_ruleset("*redis-check");
4088 if (rs == NULL) {
4089 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4090 goto error;
4091 }
4092
4093 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4094 1, curpx, &rs->rules, file, line, &errmsg);
4095 if (!chk) {
4096 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4097 goto error;
4098 }
4099 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004100 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004101
4102 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4103 "error-status", "L7STS",
4104 "on-error", "%[res.payload(0,0),cut_crlf]",
4105 "on-success", "Redis server is ok",
4106 ""},
4107 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4108 if (!chk) {
4109 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4110 goto error;
4111 }
4112 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004113 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004114
4115 ruleset_found:
4116 rules->list = &rs->rules;
4117 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4118 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4119
4120 out:
4121 free(errmsg);
4122 return err_code;
4123
4124 error:
4125 free_tcpcheck_ruleset(rs);
4126 err_code |= ERR_ALERT | ERR_FATAL;
4127 goto out;
4128}
4129
4130
4131/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004132int 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 +01004133 const char *file, int line)
4134{
4135 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4136 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4137 *
4138 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4139 */
4140 static char sslv3_client_hello[] = {
4141 "16" /* ContentType : 0x16 = Handshake */
4142 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4143 "0079" /* ContentLength : 0x79 bytes after this one */
4144 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4145 "000075" /* HandshakeLength : 0x75 bytes after this one */
4146 "0300" /* Hello Version : 0x0300 = v3 */
4147 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4148 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4149 "00" /* Session ID length : empty (no session ID) */
4150 "004E" /* Cipher Suite Length : 78 bytes after this one */
4151 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4152 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4153 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4154 "000D" "000E" "000F" "0010" /* various bit lengths, */
4155 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4156 "0015" "0016" "0017" "0018"
4157 "0019" "001A" "001B" "002F"
4158 "0030" "0031" "0032" "0033"
4159 "0034" "0035" "0036" "0037"
4160 "0038" "0039" "003A"
4161 "01" /* Compression Length : 0x01 = 1 byte for types */
4162 "00" /* Compression Type : 0x00 = NULL compression */
4163 };
4164
4165 struct tcpcheck_ruleset *rs = NULL;
4166 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4167 struct tcpcheck_rule *chk;
4168 char *errmsg = NULL;
4169 int err_code = 0;
4170
4171 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4172 err_code |= ERR_WARN;
4173
4174 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4175 goto out;
4176
4177 curpx->options2 &= ~PR_O2_CHK_ANY;
4178 curpx->options2 |= PR_O2_TCPCHK_CHK;
4179
4180 free_tcpcheck_vars(&rules->preset_vars);
4181 rules->list = NULL;
4182 rules->flags = 0;
4183
4184 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4185 if (rs)
4186 goto ruleset_found;
4187
4188 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4189 if (rs == NULL) {
4190 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4191 goto error;
4192 }
4193
4194 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4195 1, curpx, &rs->rules, file, line, &errmsg);
4196 if (!chk) {
4197 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4198 goto error;
4199 }
4200 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004201 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004202
4203 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4204 "min-recv", "5", "ok-status", "L6OK",
4205 "error-status", "L6RSP", "tout-status", "L6TOUT",
4206 ""},
4207 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4208 if (!chk) {
4209 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4210 goto error;
4211 }
4212 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004213 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004214
4215 ruleset_found:
4216 rules->list = &rs->rules;
4217 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4218 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4219
4220 out:
4221 free(errmsg);
4222 return err_code;
4223
4224 error:
4225 free_tcpcheck_ruleset(rs);
4226 err_code |= ERR_ALERT | ERR_FATAL;
4227 goto out;
4228}
4229
4230/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004231int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004232 const char *file, int line)
4233{
4234 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4235
4236 struct tcpcheck_ruleset *rs = NULL;
4237 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4238 struct tcpcheck_rule *chk;
4239 struct tcpcheck_var *var = NULL;
4240 char *cmd = NULL, *errmsg = NULL;
4241 int err_code = 0;
4242
4243 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4244 err_code |= ERR_WARN;
4245
4246 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4247 goto out;
4248
4249 curpx->options2 &= ~PR_O2_CHK_ANY;
4250 curpx->options2 |= PR_O2_TCPCHK_CHK;
4251
4252 free_tcpcheck_vars(&rules->preset_vars);
4253 rules->list = NULL;
4254 rules->flags = 0;
4255
4256 cur_arg += 2;
4257 if (*args[cur_arg] && *args[cur_arg+1] &&
4258 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4259 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4260 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4261 if (cmd)
4262 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4263 }
4264 else {
4265 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4266 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4267 cmd = strdup("HELO localhost");
4268 }
4269
4270 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4271 if (cmd == NULL || var == NULL) {
4272 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4273 goto error;
4274 }
4275 var->data.type = SMP_T_STR;
4276 var->data.u.str.area = cmd;
4277 var->data.u.str.data = strlen(cmd);
4278 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004279 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004280 cmd = NULL;
4281 var = NULL;
4282
4283 rs = find_tcpcheck_ruleset("*smtp-check");
4284 if (rs)
4285 goto ruleset_found;
4286
4287 rs = create_tcpcheck_ruleset("*smtp-check");
4288 if (rs == NULL) {
4289 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4290 goto error;
4291 }
4292
4293 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4294 1, curpx, &rs->rules, file, line, &errmsg);
4295 if (!chk) {
4296 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4297 goto error;
4298 }
4299 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004300 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004301
4302 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4303 "min-recv", "4",
4304 "error-status", "L7RSP",
4305 "on-error", "%[res.payload(0,0),cut_crlf]",
4306 ""},
4307 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4308 if (!chk) {
4309 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4310 goto error;
4311 }
4312 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004313 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004314
4315 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4316 "min-recv", "4",
4317 "error-status", "L7STS",
4318 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4319 "status-code", "res.payload(0,3)",
4320 ""},
4321 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4322 if (!chk) {
4323 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4324 goto error;
4325 }
4326 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004327 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004328
4329 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4330 1, curpx, &rs->rules, file, line, &errmsg);
4331 if (!chk) {
4332 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4333 goto error;
4334 }
4335 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004336 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004337
4338 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4339 "min-recv", "4",
4340 "error-status", "L7STS",
4341 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4342 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4343 "status-code", "res.payload(0,3)",
4344 ""},
4345 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4346 if (!chk) {
4347 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4348 goto error;
4349 }
4350 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004351 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004352
4353 ruleset_found:
4354 rules->list = &rs->rules;
4355 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4356 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4357
4358 out:
4359 free(errmsg);
4360 return err_code;
4361
4362 error:
4363 free(cmd);
4364 free(var);
4365 free_tcpcheck_vars(&rules->preset_vars);
4366 free_tcpcheck_ruleset(rs);
4367 err_code |= ERR_ALERT | ERR_FATAL;
4368 goto out;
4369}
4370
4371/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004372int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004373 const char *file, int line)
4374{
4375 static char pgsql_req[] = {
4376 "%[var(check.plen),htonl,hex]" /* The packet length*/
4377 "00030000" /* the version 3.0 */
4378 "7573657200" /* "user" key */
4379 "%[var(check.username),hex]00" /* the username */
4380 "00"
4381 };
4382
4383 struct tcpcheck_ruleset *rs = NULL;
4384 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4385 struct tcpcheck_rule *chk;
4386 struct tcpcheck_var *var = NULL;
4387 char *user = NULL, *errmsg = NULL;
4388 size_t packetlen = 0;
4389 int err_code = 0;
4390
4391 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4392 err_code |= ERR_WARN;
4393
4394 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4395 goto out;
4396
4397 curpx->options2 &= ~PR_O2_CHK_ANY;
4398 curpx->options2 |= PR_O2_TCPCHK_CHK;
4399
4400 free_tcpcheck_vars(&rules->preset_vars);
4401 rules->list = NULL;
4402 rules->flags = 0;
4403
4404 cur_arg += 2;
4405 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4406 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4407 file, line, args[0], args[1]);
4408 goto error;
4409 }
4410 if (strcmp(args[cur_arg], "user") == 0) {
4411 packetlen = 15 + strlen(args[cur_arg+1]);
4412 user = strdup(args[cur_arg+1]);
4413
4414 var = create_tcpcheck_var(ist("check.username"));
4415 if (user == NULL || var == NULL) {
4416 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4417 goto error;
4418 }
4419 var->data.type = SMP_T_STR;
4420 var->data.u.str.area = user;
4421 var->data.u.str.data = strlen(user);
4422 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004423 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004424 user = NULL;
4425 var = NULL;
4426
4427 var = create_tcpcheck_var(ist("check.plen"));
4428 if (var == NULL) {
4429 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4430 goto error;
4431 }
4432 var->data.type = SMP_T_SINT;
4433 var->data.u.sint = packetlen;
4434 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004435 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004436 var = NULL;
4437 }
4438 else {
4439 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4440 file, line, args[0], args[1]);
4441 goto error;
4442 }
4443
4444 rs = find_tcpcheck_ruleset("*pgsql-check");
4445 if (rs)
4446 goto ruleset_found;
4447
4448 rs = create_tcpcheck_ruleset("*pgsql-check");
4449 if (rs == NULL) {
4450 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4451 goto error;
4452 }
4453
4454 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4455 1, curpx, &rs->rules, file, line, &errmsg);
4456 if (!chk) {
4457 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4458 goto error;
4459 }
4460 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004461 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004462
4463 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4464 1, curpx, &rs->rules, file, line, &errmsg);
4465 if (!chk) {
4466 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4467 goto error;
4468 }
4469 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004470 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004471
4472 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4473 "min-recv", "5",
4474 "error-status", "L7RSP",
4475 "on-error", "%[res.payload(6,0)]",
4476 ""},
4477 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4478 if (!chk) {
4479 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4480 goto error;
4481 }
4482 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004483 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004484
4485 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4486 "min-recv", "9",
4487 "error-status", "L7STS",
4488 "on-success", "PostgreSQL server is ok",
4489 "on-error", "PostgreSQL unknown error",
4490 ""},
4491 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4492 if (!chk) {
4493 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4494 goto error;
4495 }
4496 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004497 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004498
4499 ruleset_found:
4500 rules->list = &rs->rules;
4501 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4502 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4503
4504 out:
4505 free(errmsg);
4506 return err_code;
4507
4508 error:
4509 free(user);
4510 free(var);
4511 free_tcpcheck_vars(&rules->preset_vars);
4512 free_tcpcheck_ruleset(rs);
4513 err_code |= ERR_ALERT | ERR_FATAL;
4514 goto out;
4515}
4516
4517
4518/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004519int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004520 const char *file, int line)
4521{
4522 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4523 * const char mysql40_client_auth_pkt[] = {
4524 * "\x0e\x00\x00" // packet length
4525 * "\x01" // packet number
4526 * "\x00\x00" // client capabilities
4527 * "\x00\x00\x01" // max packet
4528 * "haproxy\x00" // username (null terminated string)
4529 * "\x00" // filler (always 0x00)
4530 * "\x01\x00\x00" // packet length
4531 * "\x00" // packet number
4532 * "\x01" // COM_QUIT command
4533 * };
4534 */
4535 static char mysql40_rsname[] = "*mysql40-check";
4536 static char mysql40_req[] = {
4537 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4538 "0080" /* client capabilities */
4539 "000001" /* max packet */
4540 "%[var(check.username),hex]00" /* the username */
4541 "00" /* filler (always 0x00) */
4542 "010000" /* packet length*/
4543 "00" /* sequence ID */
4544 "01" /* COM_QUIT command */
4545 };
4546
4547 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4548 * const char mysql41_client_auth_pkt[] = {
4549 * "\x0e\x00\x00\" // packet length
4550 * "\x01" // packet number
4551 * "\x00\x00\x00\x00" // client capabilities
4552 * "\x00\x00\x00\x01" // max packet
4553 * "\x21" // character set (UTF-8)
4554 * char[23] // All zeroes
4555 * "haproxy\x00" // username (null terminated string)
4556 * "\x00" // filler (always 0x00)
4557 * "\x01\x00\x00" // packet length
4558 * "\x00" // packet number
4559 * "\x01" // COM_QUIT command
4560 * };
4561 */
4562 static char mysql41_rsname[] = "*mysql41-check";
4563 static char mysql41_req[] = {
4564 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4565 "00820000" /* client capabilities */
4566 "00800001" /* max packet */
4567 "21" /* character set (UTF-8) */
4568 "000000000000000000000000" /* 23 bytes, al zeroes */
4569 "0000000000000000000000"
4570 "%[var(check.username),hex]00" /* the username */
4571 "00" /* filler (always 0x00) */
4572 "010000" /* packet length*/
4573 "00" /* sequence ID */
4574 "01" /* COM_QUIT command */
4575 };
4576
4577 struct tcpcheck_ruleset *rs = NULL;
4578 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4579 struct tcpcheck_rule *chk;
4580 struct tcpcheck_var *var = NULL;
4581 char *mysql_rsname = "*mysql-check";
4582 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4583 int index = 0, err_code = 0;
4584
4585 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4586 err_code |= ERR_WARN;
4587
4588 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4589 goto out;
4590
4591 curpx->options2 &= ~PR_O2_CHK_ANY;
4592 curpx->options2 |= PR_O2_TCPCHK_CHK;
4593
4594 free_tcpcheck_vars(&rules->preset_vars);
4595 rules->list = NULL;
4596 rules->flags = 0;
4597
4598 cur_arg += 2;
4599 if (*args[cur_arg]) {
4600 int packetlen, userlen;
4601
4602 if (strcmp(args[cur_arg], "user") != 0) {
4603 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4604 file, line, args[0], args[1], args[cur_arg]);
4605 goto error;
4606 }
4607
4608 if (*(args[cur_arg+1]) == 0) {
4609 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4610 file, line, args[0], args[1], args[cur_arg]);
4611 goto error;
4612 }
4613
4614 hdr = calloc(4, sizeof(*hdr));
4615 user = strdup(args[cur_arg+1]);
4616 userlen = strlen(args[cur_arg+1]);
4617
4618 if (hdr == NULL || user == NULL) {
4619 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4620 goto error;
4621 }
4622
4623 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4624 packetlen = userlen + 7 + 27;
4625 mysql_req = mysql41_req;
4626 mysql_rsname = mysql41_rsname;
4627 }
4628 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4629 packetlen = userlen + 7;
4630 mysql_req = mysql40_req;
4631 mysql_rsname = mysql40_rsname;
4632 }
4633 else {
4634 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4635 file, line, args[cur_arg], args[cur_arg+2]);
4636 goto error;
4637 }
4638
4639 hdr[0] = (unsigned char)(packetlen & 0xff);
4640 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4641 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4642 hdr[3] = 1;
4643
4644 var = create_tcpcheck_var(ist("check.header"));
4645 if (var == NULL) {
4646 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4647 goto error;
4648 }
4649 var->data.type = SMP_T_STR;
4650 var->data.u.str.area = hdr;
4651 var->data.u.str.data = 4;
4652 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004653 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004654 hdr = NULL;
4655 var = NULL;
4656
4657 var = create_tcpcheck_var(ist("check.username"));
4658 if (var == NULL) {
4659 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4660 goto error;
4661 }
4662 var->data.type = SMP_T_STR;
4663 var->data.u.str.area = user;
4664 var->data.u.str.data = strlen(user);
4665 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004666 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004667 user = NULL;
4668 var = NULL;
4669 }
4670
4671 rs = find_tcpcheck_ruleset(mysql_rsname);
4672 if (rs)
4673 goto ruleset_found;
4674
4675 rs = create_tcpcheck_ruleset(mysql_rsname);
4676 if (rs == NULL) {
4677 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4678 goto error;
4679 }
4680
4681 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4682 1, curpx, &rs->rules, file, line, &errmsg);
4683 if (!chk) {
4684 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4685 goto error;
4686 }
4687 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004688 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004689
4690 if (mysql_req) {
4691 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4692 1, curpx, &rs->rules, file, line, &errmsg);
4693 if (!chk) {
4694 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4695 goto error;
4696 }
4697 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004698 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004699 }
4700
4701 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4702 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4703 if (!chk) {
4704 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4705 goto error;
4706 }
4707 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4708 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004709 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004710
4711 if (mysql_req) {
4712 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4713 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4714 if (!chk) {
4715 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4716 goto error;
4717 }
4718 chk->expect.custom = tcpcheck_mysql_expect_ok;
4719 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004720 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004721 }
4722
4723 ruleset_found:
4724 rules->list = &rs->rules;
4725 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4726 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4727
4728 out:
4729 free(errmsg);
4730 return err_code;
4731
4732 error:
4733 free(hdr);
4734 free(user);
4735 free(var);
4736 free_tcpcheck_vars(&rules->preset_vars);
4737 free_tcpcheck_ruleset(rs);
4738 err_code |= ERR_ALERT | ERR_FATAL;
4739 goto out;
4740}
4741
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004742int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004743 const char *file, int line)
4744{
4745 static char *ldap_req = "300C020101600702010304008000";
4746
4747 struct tcpcheck_ruleset *rs = NULL;
4748 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4749 struct tcpcheck_rule *chk;
4750 char *errmsg = NULL;
4751 int err_code = 0;
4752
4753 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4754 err_code |= ERR_WARN;
4755
4756 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4757 goto out;
4758
4759 curpx->options2 &= ~PR_O2_CHK_ANY;
4760 curpx->options2 |= PR_O2_TCPCHK_CHK;
4761
4762 free_tcpcheck_vars(&rules->preset_vars);
4763 rules->list = NULL;
4764 rules->flags = 0;
4765
4766 rs = find_tcpcheck_ruleset("*ldap-check");
4767 if (rs)
4768 goto ruleset_found;
4769
4770 rs = create_tcpcheck_ruleset("*ldap-check");
4771 if (rs == NULL) {
4772 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4773 goto error;
4774 }
4775
4776 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4777 1, curpx, &rs->rules, file, line, &errmsg);
4778 if (!chk) {
4779 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4780 goto error;
4781 }
4782 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004783 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004784
4785 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4786 "min-recv", "14",
4787 "on-error", "Not LDAPv3 protocol",
4788 ""},
4789 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4790 if (!chk) {
4791 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4792 goto error;
4793 }
4794 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004795 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004796
4797 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4798 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4799 if (!chk) {
4800 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4801 goto error;
4802 }
4803 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4804 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004805 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004806
4807 ruleset_found:
4808 rules->list = &rs->rules;
4809 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4810 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4811
4812 out:
4813 free(errmsg);
4814 return err_code;
4815
4816 error:
4817 free_tcpcheck_ruleset(rs);
4818 err_code |= ERR_ALERT | ERR_FATAL;
4819 goto out;
4820}
4821
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004822int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004823 const char *file, int line)
4824{
4825 struct tcpcheck_ruleset *rs = NULL;
4826 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4827 struct tcpcheck_rule *chk;
4828 char *spop_req = NULL;
4829 char *errmsg = NULL;
4830 int spop_len = 0, err_code = 0;
4831
4832 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4833 err_code |= ERR_WARN;
4834
4835 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4836 goto out;
4837
4838 curpx->options2 &= ~PR_O2_CHK_ANY;
4839 curpx->options2 |= PR_O2_TCPCHK_CHK;
4840
4841 free_tcpcheck_vars(&rules->preset_vars);
4842 rules->list = NULL;
4843 rules->flags = 0;
4844
4845
4846 rs = find_tcpcheck_ruleset("*spop-check");
4847 if (rs)
4848 goto ruleset_found;
4849
4850 rs = create_tcpcheck_ruleset("*spop-check");
4851 if (rs == NULL) {
4852 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4853 goto error;
4854 }
4855
4856 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4857 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4858 goto error;
4859 }
4860 chunk_reset(&trash);
4861 dump_binary(&trash, spop_req, spop_len);
4862 trash.area[trash.data] = '\0';
4863
4864 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4865 1, curpx, &rs->rules, file, line, &errmsg);
4866 if (!chk) {
4867 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4868 goto error;
4869 }
4870 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004871 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004872
4873 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4874 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4875 if (!chk) {
4876 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4877 goto error;
4878 }
4879 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4880 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004881 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004882
4883 ruleset_found:
4884 rules->list = &rs->rules;
4885 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4886 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4887
4888 out:
4889 free(spop_req);
4890 free(errmsg);
4891 return err_code;
4892
4893 error:
4894 free_tcpcheck_ruleset(rs);
4895 err_code |= ERR_ALERT | ERR_FATAL;
4896 goto out;
4897}
4898
4899
4900static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4901{
4902 struct tcpcheck_rule *chk = NULL;
4903 struct tcpcheck_http_hdr *hdr = NULL;
4904 char *meth = NULL, *uri = NULL, *vsn = NULL;
4905 char *hdrs, *body;
4906
4907 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4908 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4909 if (hdrs == body)
4910 hdrs = NULL;
4911 if (hdrs) {
4912 *hdrs = '\0';
4913 hdrs +=2;
4914 }
4915 if (body) {
4916 *body = '\0';
4917 body += 4;
4918 }
4919 if (hdrs || body) {
4920 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4921 " Please, consider to use 'http-check send' directive instead.");
4922 }
4923
4924 chk = calloc(1, sizeof(*chk));
4925 if (!chk) {
4926 memprintf(errmsg, "out of memory");
4927 goto error;
4928 }
4929 chk->action = TCPCHK_ACT_SEND;
4930 chk->send.type = TCPCHK_SEND_HTTP;
4931 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4932 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4933 LIST_INIT(&chk->send.http.hdrs);
4934
4935 /* Copy the method, uri and version */
4936 if (*args[cur_arg]) {
4937 if (!*args[cur_arg+1])
4938 uri = args[cur_arg];
4939 else
4940 meth = args[cur_arg];
4941 }
4942 if (*args[cur_arg+1])
4943 uri = args[cur_arg+1];
4944 if (*args[cur_arg+2])
4945 vsn = args[cur_arg+2];
4946
4947 if (meth) {
4948 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4949 chk->send.http.meth.str.area = strdup(meth);
4950 chk->send.http.meth.str.data = strlen(meth);
4951 if (!chk->send.http.meth.str.area) {
4952 memprintf(errmsg, "out of memory");
4953 goto error;
4954 }
4955 }
4956 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004957 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004958 if (!isttest(chk->send.http.uri)) {
4959 memprintf(errmsg, "out of memory");
4960 goto error;
4961 }
4962 }
4963 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004964 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004965 if (!isttest(chk->send.http.vsn)) {
4966 memprintf(errmsg, "out of memory");
4967 goto error;
4968 }
4969 }
4970
4971 /* Copy the header */
4972 if (hdrs) {
4973 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4974 struct h1m h1m;
4975 int i, ret;
4976
4977 /* Build and parse the request */
4978 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4979
4980 h1m.flags = H1_MF_HDRS_ONLY;
4981 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4982 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4983 &h1m, NULL);
4984 if (ret <= 0) {
4985 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4986 goto error;
4987 }
4988
4989 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4990 hdr = calloc(1, sizeof(*hdr));
4991 if (!hdr) {
4992 memprintf(errmsg, "out of memory");
4993 goto error;
4994 }
4995 LIST_INIT(&hdr->value);
4996 hdr->name = istdup(tmp_hdrs[i].n);
4997 if (!hdr->name.ptr) {
4998 memprintf(errmsg, "out of memory");
4999 goto error;
5000 }
5001
5002 ist0(tmp_hdrs[i].v);
5003 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5004 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005005 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005006 }
5007 }
5008
5009 /* Copy the body */
5010 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005011 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005012 if (!isttest(chk->send.http.body)) {
5013 memprintf(errmsg, "out of memory");
5014 goto error;
5015 }
5016 }
5017
5018 return chk;
5019
5020 error:
5021 free_tcpcheck_http_hdr(hdr);
5022 free_tcpcheck(chk, 0);
5023 return NULL;
5024}
5025
5026/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005027int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005028 const char *file, int line)
5029{
5030 struct tcpcheck_ruleset *rs = NULL;
5031 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5032 struct tcpcheck_rule *chk;
5033 char *errmsg = NULL;
5034 int err_code = 0;
5035
5036 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5037 err_code |= ERR_WARN;
5038
5039 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5040 goto out;
5041
5042 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5043 if (!chk) {
5044 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5045 goto error;
5046 }
5047 if (errmsg) {
5048 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5049 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005050 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005051 }
5052
5053 no_request:
5054 curpx->options2 &= ~PR_O2_CHK_ANY;
5055 curpx->options2 |= PR_O2_TCPCHK_CHK;
5056
5057 free_tcpcheck_vars(&rules->preset_vars);
5058 rules->list = NULL;
5059 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5060
5061 /* Deduce the ruleset name from the proxy info */
5062 chunk_printf(&trash, "*http-check-%s_%s-%d",
5063 ((curpx == defpx) ? "defaults" : curpx->id),
5064 curpx->conf.file, curpx->conf.line);
5065
5066 rs = find_tcpcheck_ruleset(b_orig(&trash));
5067 if (rs == NULL) {
5068 rs = create_tcpcheck_ruleset(b_orig(&trash));
5069 if (rs == NULL) {
5070 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5071 goto error;
5072 }
5073 }
5074
5075 rules->list = &rs->rules;
5076 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5077 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5078 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5079 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5080 rules->list = NULL;
5081 goto error;
5082 }
5083
5084 out:
5085 free(errmsg);
5086 return err_code;
5087
5088 error:
5089 free_tcpcheck_ruleset(rs);
5090 free_tcpcheck(chk, 0);
5091 err_code |= ERR_ALERT | ERR_FATAL;
5092 goto out;
5093}
5094
5095/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005096int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005097 const char *file, int line)
5098{
5099 struct tcpcheck_ruleset *rs = NULL;
5100 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5101 int err_code = 0;
5102
5103 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5104 err_code |= ERR_WARN;
5105
5106 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5107 goto out;
5108
5109 curpx->options2 &= ~PR_O2_CHK_ANY;
5110 curpx->options2 |= PR_O2_TCPCHK_CHK;
5111
5112 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5113 /* If a tcp-check rulesset is already set, do nothing */
5114 if (rules->list)
5115 goto out;
5116
5117 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5118 * get it.
5119 */
5120 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5121 goto curpx_ruleset;
5122
5123 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5124 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5125 rs = find_tcpcheck_ruleset(b_orig(&trash));
5126 if (rs)
5127 goto ruleset_found;
5128 }
5129
5130 curpx_ruleset:
5131 /* Deduce the ruleset name from the proxy info */
5132 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5133 ((curpx == defpx) ? "defaults" : curpx->id),
5134 curpx->conf.file, curpx->conf.line);
5135
5136 rs = find_tcpcheck_ruleset(b_orig(&trash));
5137 if (rs == NULL) {
5138 rs = create_tcpcheck_ruleset(b_orig(&trash));
5139 if (rs == NULL) {
5140 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5141 goto error;
5142 }
5143 }
5144
5145 ruleset_found:
5146 free_tcpcheck_vars(&rules->preset_vars);
5147 rules->list = &rs->rules;
5148 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5149 rules->flags |= TCPCHK_RULES_TCP_CHK;
5150
5151 out:
5152 return err_code;
5153
5154 error:
5155 err_code |= ERR_ALERT | ERR_FATAL;
5156 goto out;
5157}
5158
Willy Tarreau51cd5952020-06-05 12:25:38 +02005159static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005160 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005161 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5162 { 0, NULL, NULL },
5163}};
5164
5165REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5166REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5167REGISTER_POST_DEINIT(deinit_tcpchecks);
5168INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);