blob: 8eefc4eaa84915c51c9458e54d12027f744639c1 [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>
wrightlawd67b10f2022-09-08 16:10:48 +01009 * Crown Copyright 2022 Defence Science and Technology Laboratory <dstlipgroup@dstl.gov.uk>
Willy Tarreau51cd5952020-06-05 12:25:38 +020010 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version
14 * 2 of the License, or (at your option) any later version.
15 *
16 */
17
18#include <sys/resource.h>
19#include <sys/socket.h>
20#include <sys/types.h>
21#include <sys/wait.h>
22#include <netinet/in.h>
23#include <netinet/tcp.h>
24#include <arpa/inet.h>
25
26#include <ctype.h>
27#include <errno.h>
28#include <fcntl.h>
29#include <signal.h>
30#include <stdarg.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <time.h>
35#include <unistd.h>
36
37#include <haproxy/action.h>
38#include <haproxy/api.h>
39#include <haproxy/cfgparse.h>
40#include <haproxy/check.h>
41#include <haproxy/chunk.h>
42#include <haproxy/connection.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020043#include <haproxy/errors.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020044#include <haproxy/global.h>
45#include <haproxy/h1.h>
46#include <haproxy/http.h>
47#include <haproxy/http_htx.h>
48#include <haproxy/htx.h>
49#include <haproxy/istbuf.h>
50#include <haproxy/list.h>
51#include <haproxy/log.h>
Christopher Faulet81011212021-09-16 16:01:09 +020052#include <haproxy/net_helper.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020053#include <haproxy/protocol.h>
54#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020055#include <haproxy/regex.h>
56#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020057#include <haproxy/server.h>
58#include <haproxy/ssl_sock.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020059#include <haproxy/task.h>
60#include <haproxy/tcpcheck.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020061#include <haproxy/time.h>
62#include <haproxy/tools.h>
Christopher Faulet147b8c92021-04-10 09:00:38 +020063#include <haproxy/trace.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020064#include <haproxy/vars.h>
65
66
Christopher Faulet147b8c92021-04-10 09:00:38 +020067#define TRACE_SOURCE &trace_check
68
Willy Tarreau51cd5952020-06-05 12:25:38 +020069/* Global tree to share all tcp-checks */
70struct eb_root shared_tcpchecks = EB_ROOT;
71
72
73DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
74
75/**************************************************************************/
76/*************** Init/deinit tcp-check rules and ruleset ******************/
77/**************************************************************************/
78/* Releases memory allocated for a log-format string */
79static void free_tcpcheck_fmt(struct list *fmt)
80{
81 struct logformat_node *lf, *lfb;
82
83 list_for_each_entry_safe(lf, lfb, fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +020084 LIST_DELETE(&lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +020085 release_sample_expr(lf->expr);
86 free(lf->arg);
87 free(lf);
88 }
89}
90
91/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
92void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
93{
94 if (!hdr)
95 return;
96
97 free_tcpcheck_fmt(&hdr->value);
98 istfree(&hdr->name);
99 free(hdr);
100}
101
102/* Releases memory allocated for an HTTP header list used in a tcp-check send
103 * rule
104 */
105static void free_tcpcheck_http_hdrs(struct list *hdrs)
106{
107 struct tcpcheck_http_hdr *hdr, *bhdr;
108
109 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200110 LIST_DELETE(&hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200111 free_tcpcheck_http_hdr(hdr);
112 }
113}
114
115/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
116 * tcp-check was allocated using a memory pool (it is used to instantiate email
117 * alerts).
118 */
119void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
120{
121 if (!rule)
122 return;
123
124 free(rule->comment);
125 switch (rule->action) {
126 case TCPCHK_ACT_SEND:
127 switch (rule->send.type) {
128 case TCPCHK_SEND_STRING:
129 case TCPCHK_SEND_BINARY:
130 istfree(&rule->send.data);
131 break;
132 case TCPCHK_SEND_STRING_LF:
133 case TCPCHK_SEND_BINARY_LF:
134 free_tcpcheck_fmt(&rule->send.fmt);
135 break;
136 case TCPCHK_SEND_HTTP:
137 free(rule->send.http.meth.str.area);
138 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
139 istfree(&rule->send.http.uri);
140 else
141 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
142 istfree(&rule->send.http.vsn);
143 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
144 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
145 istfree(&rule->send.http.body);
146 else
147 free_tcpcheck_fmt(&rule->send.http.body_fmt);
148 break;
149 case TCPCHK_SEND_UNDEF:
150 break;
151 }
152 break;
153 case TCPCHK_ACT_EXPECT:
154 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
155 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
156 release_sample_expr(rule->expect.status_expr);
157 switch (rule->expect.type) {
158 case TCPCHK_EXPECT_HTTP_STATUS:
159 free(rule->expect.codes.codes);
160 break;
161 case TCPCHK_EXPECT_STRING:
162 case TCPCHK_EXPECT_BINARY:
163 case TCPCHK_EXPECT_HTTP_BODY:
164 istfree(&rule->expect.data);
165 break;
166 case TCPCHK_EXPECT_STRING_REGEX:
167 case TCPCHK_EXPECT_BINARY_REGEX:
168 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
169 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
170 regex_free(rule->expect.regex);
171 break;
172 case TCPCHK_EXPECT_STRING_LF:
173 case TCPCHK_EXPECT_BINARY_LF:
174 case TCPCHK_EXPECT_HTTP_BODY_LF:
175 free_tcpcheck_fmt(&rule->expect.fmt);
176 break;
177 case TCPCHK_EXPECT_HTTP_HEADER:
178 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
179 regex_free(rule->expect.hdr.name_re);
180 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
181 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
182 else
183 istfree(&rule->expect.hdr.name);
184
185 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
186 regex_free(rule->expect.hdr.value_re);
187 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
188 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
189 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
190 istfree(&rule->expect.hdr.value);
191 break;
192 case TCPCHK_EXPECT_CUSTOM:
193 case TCPCHK_EXPECT_UNDEF:
194 break;
195 }
196 break;
197 case TCPCHK_ACT_CONNECT:
198 free(rule->connect.sni);
199 free(rule->connect.alpn);
200 release_sample_expr(rule->connect.port_expr);
201 break;
202 case TCPCHK_ACT_COMMENT:
203 break;
204 case TCPCHK_ACT_ACTION_KW:
205 free(rule->action_kw.rule);
206 break;
207 }
208
209 if (in_pool)
210 pool_free(pool_head_tcpcheck_rule, rule);
211 else
212 free(rule);
213}
214
215/* Creates a tcp-check variable used in preset variables before executing a
216 * tcp-check ruleset.
217 */
218struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
219{
220 struct tcpcheck_var *var = NULL;
221
222 var = calloc(1, sizeof(*var));
223 if (var == NULL)
224 return NULL;
225
226 var->name = istdup(name);
227 if (!isttest(var->name)) {
228 free(var);
229 return NULL;
230 }
231
232 LIST_INIT(&var->list);
233 return var;
234}
235
236/* Releases memory allocated for a preset tcp-check variable */
237void free_tcpcheck_var(struct tcpcheck_var *var)
238{
239 if (!var)
240 return;
241
242 istfree(&var->name);
243 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
244 free(var->data.u.str.area);
245 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
246 free(var->data.u.meth.str.area);
247 free(var);
248}
249
250/* Releases a list of preset tcp-check variables */
251void free_tcpcheck_vars(struct list *vars)
252{
253 struct tcpcheck_var *var, *back;
254
255 list_for_each_entry_safe(var, back, vars, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200256 LIST_DELETE(&var->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200257 free_tcpcheck_var(var);
258 }
259}
260
261/* Duplicate a list of preset tcp-check variables */
Willy Tarreau09f2e772021-02-12 08:42:30 +0100262int dup_tcpcheck_vars(struct list *dst, const struct list *src)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200263{
Willy Tarreau09f2e772021-02-12 08:42:30 +0100264 const struct tcpcheck_var *var;
265 struct tcpcheck_var *new = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200266
267 list_for_each_entry(var, src, list) {
268 new = create_tcpcheck_var(var->name);
269 if (!new)
270 goto error;
271 new->data.type = var->data.type;
272 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
273 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
274 goto error;
275 if (var->data.type == SMP_T_STR)
276 new->data.u.str.area[new->data.u.str.data] = 0;
277 }
278 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
279 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
280 goto error;
281 new->data.u.str.area[new->data.u.str.data] = 0;
282 new->data.u.meth.meth = var->data.u.meth.meth;
283 }
284 else
285 new->data.u = var->data.u;
Willy Tarreau2b718102021-04-21 07:32:39 +0200286 LIST_APPEND(dst, &new->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200287 }
288 return 1;
289
290 error:
291 free(new);
292 return 0;
293}
294
295/* Looks for a shared tcp-check ruleset given its name. */
296struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
297{
298 struct tcpcheck_ruleset *rs;
299 struct ebpt_node *node;
300
301 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
302 if (node) {
303 rs = container_of(node, typeof(*rs), node);
304 return rs;
305 }
306 return NULL;
307}
308
309/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks
310 * tree.
311 */
312struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
313{
314 struct tcpcheck_ruleset *rs;
315
316 rs = calloc(1, sizeof(*rs));
317 if (rs == NULL)
318 return NULL;
319
320 rs->node.key = strdup(name);
321 if (rs->node.key == NULL) {
322 free(rs);
323 return NULL;
324 }
325
326 LIST_INIT(&rs->rules);
327 ebis_insert(&shared_tcpchecks, &rs->node);
328 return rs;
329}
330
331/* Releases memory allocated by a tcp-check ruleset. */
332void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
333{
334 struct tcpcheck_rule *r, *rb;
335
336 if (!rs)
337 return;
338
339 ebpt_delete(&rs->node);
340 free(rs->node.key);
341 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200342 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200343 free_tcpcheck(r, 0);
344 }
345 free(rs);
346}
347
348
349/**************************************************************************/
350/**************** Everything about tcp-checks execution *******************/
351/**************************************************************************/
352/* Returns the id of a step in a tcp-check ruleset */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200353int tcpcheck_get_step_id(const struct check *check, const struct tcpcheck_rule *rule)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200354{
355 if (!rule)
356 rule = check->current_step;
357
358 /* no last started step => first step */
359 if (!rule)
360 return 1;
361
362 /* last step is the first implicit connect */
363 if (rule->index == 0 &&
364 rule->action == TCPCHK_ACT_CONNECT &&
365 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
366 return 0;
367
368 return rule->index + 1;
369}
370
371/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
372 * NULL if none was found.
373 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200374struct tcpcheck_rule *get_first_tcpcheck_rule(const struct tcpcheck_rules *rules)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200375{
376 struct tcpcheck_rule *r;
377
378 list_for_each_entry(r, rules->list, list) {
379 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
380 return r;
381 }
382 return NULL;
383}
384
385/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
386 * NULL if none was found.
387 */
388static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
389{
390 struct tcpcheck_rule *r;
391
392 list_for_each_entry_rev(r, rules->list, list) {
393 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
394 return r;
395 }
396 return NULL;
397}
398
399/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
400 * <start> or NULL if non was found. If <start> is NULL, it relies on
401 * get_first_tcpcheck_rule().
402 */
403static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
404{
405 struct tcpcheck_rule *r;
406
407 if (!start)
408 return get_first_tcpcheck_rule(rules);
409
410 r = LIST_NEXT(&start->list, typeof(r), list);
411 list_for_each_entry_from(r, rules->list, list) {
412 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
413 return r;
414 }
415 return NULL;
416}
417
418
419/* Creates info message when a tcp-check healthcheck fails on an expect rule */
420static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
421 int match, struct ist info)
422{
423 struct sample *smp;
424
425 /* Follows these step to produce the info message:
426 * 1. if info field is already provided, copy it
427 * 2. if the expect rule provides an onerror log-format string,
428 * use it to produce the message
429 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
430 * 4. Otherwise produce the generic tcp-check info message
431 */
432 if (istlen(info)) {
433 chunk_strncat(msg, istptr(info), istlen(info));
434 goto comment;
435 }
436 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
437 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
438 goto comment;
439 }
440
441 if (check->type == PR_O2_TCPCHK_CHK &&
442 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
443 goto comment;
444
445 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
446 switch (rule->expect.type) {
447 case TCPCHK_EXPECT_HTTP_STATUS:
448 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
449 break;
450 case TCPCHK_EXPECT_STRING:
451 case TCPCHK_EXPECT_HTTP_BODY:
452 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
453 tcpcheck_get_step_id(check, rule));
454 break;
455 case TCPCHK_EXPECT_BINARY:
456 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
457 break;
458 case TCPCHK_EXPECT_STRING_REGEX:
459 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
460 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
461 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
462 break;
463 case TCPCHK_EXPECT_BINARY_REGEX:
464 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
465 break;
466 case TCPCHK_EXPECT_STRING_LF:
467 case TCPCHK_EXPECT_HTTP_BODY_LF:
468 chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule));
469 break;
470 case TCPCHK_EXPECT_BINARY_LF:
471 chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule));
472 break;
473 case TCPCHK_EXPECT_CUSTOM:
474 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
475 break;
476 case TCPCHK_EXPECT_HTTP_HEADER:
477 chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
478 case TCPCHK_EXPECT_UNDEF:
479 /* Should never happen. */
480 return;
481 }
482
483 comment:
484 /* If the failing expect rule provides a comment, it is concatenated to
485 * the info message.
486 */
487 if (rule->comment) {
488 chunk_strcat(msg, " comment: ");
489 chunk_strcat(msg, rule->comment);
490 }
491
492 /* Finally, the check status code is set if the failing expect rule
493 * defines a status expression.
494 */
495 if (rule->expect.status_expr) {
496 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
497 rule->expect.status_expr, SMP_T_STR);
498
499 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
500 sample_casts[smp->data.type][SMP_T_SINT](smp))
501 check->code = smp->data.u.sint;
502 }
503
504 *(b_tail(msg)) = '\0';
505}
506
507/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
508static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
509 struct ist info)
510{
511 struct sample *smp;
512
513 /* Follows these step to produce the info message:
514 * 1. if info field is already provided, copy it
515 * 2. if the expect rule provides an onsucces log-format string,
516 * use it to produce the message
517 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
518 * 4. Otherwise produce the generic tcp-check info message
519 */
520 if (istlen(info))
521 chunk_strncat(msg, istptr(info), istlen(info));
522 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
523 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
524 &rule->expect.onsuccess_fmt);
525 else if (check->type == PR_O2_TCPCHK_CHK &&
526 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
527 chunk_strcat(msg, "(tcp-check)");
528
529 /* Finally, the check status code is set if the expect rule defines a
530 * status expression.
531 */
532 if (rule->expect.status_expr) {
533 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
534 rule->expect.status_expr, SMP_T_STR);
535
536 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
537 sample_casts[smp->data.type][SMP_T_SINT](smp))
538 check->code = smp->data.u.sint;
539 }
540
541 *(b_tail(msg)) = '\0';
542}
543
544/* Internal functions to parse and validate a MySQL packet in the context of an
545 * expect rule. It start to parse the input buffer at the offset <offset>. If
546 * <last_read> is set, no more data are expected.
547 */
548static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
549 unsigned int offset, int last_read)
550{
551 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
552 enum healthcheck_status status;
553 struct buffer *msg = NULL;
554 struct ist desc = IST_NULL;
555 unsigned int err = 0, plen = 0;
556
557
Christopher Faulet147b8c92021-04-10 09:00:38 +0200558 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
559
Willy Tarreau51cd5952020-06-05 12:25:38 +0200560 /* 3 Bytes for the packet length and 1 byte for the sequence id */
561 if (b_data(&check->bi) < offset+4) {
562 if (!last_read)
563 goto wait_more_data;
564
565 /* invalid length or truncated response */
566 status = HCHK_STATUS_L7RSP;
567 goto error;
568 }
569
570 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
571 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
572 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
573
574 if (b_data(&check->bi) < offset+plen+4) {
575 if (!last_read)
576 goto wait_more_data;
577
578 /* invalid length or truncated response */
579 status = HCHK_STATUS_L7RSP;
580 goto error;
581 }
582
583 if (*b_peek(&check->bi, offset+4) == '\xff') {
584 /* MySQL Error packet always begin with field_count = 0xff */
585 status = HCHK_STATUS_L7STS;
586 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
587 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
588 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
589 goto error;
590 }
591
592 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
593 /* Not the last rule, continue */
594 goto out;
595 }
596
597 /* We set the MySQL Version in description for information purpose
598 * FIXME : it can be cool to use MySQL Version for other purpose,
599 * like mark as down old MySQL server.
600 */
601 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
602 set_server_check_status(check, status, b_peek(&check->bi, 5));
603
604 out:
605 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200606 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200607 return ret;
608
609 error:
610 ret = TCPCHK_EVAL_STOP;
611 check->code = err;
612 msg = alloc_trash_chunk();
613 if (msg)
614 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
615 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
616 goto out;
617
618 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200619 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200620 ret = TCPCHK_EVAL_WAIT;
621 goto out;
622}
623
624/* Custom tcp-check expect function to parse and validate the MySQL initial
625 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
626 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
627 * error occurred.
628 */
629enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
630{
631 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
632}
633
634/* Custom tcp-check expect function to parse and validate the MySQL OK packet
635 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
636 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
637 * an error occurred.
638 */
639enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
640{
641 unsigned int hslen = 0;
642
643 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
644 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
645 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
646
647 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
648}
649
650/* Custom tcp-check expect function to parse and validate the LDAP bind response
651 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
652 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
653 * error occurred.
654 */
655enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
656{
657 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
658 enum healthcheck_status status;
659 struct buffer *msg = NULL;
660 struct ist desc = IST_NULL;
Christopher Faulet81011212021-09-16 16:01:09 +0200661 char *ptr;
662 unsigned short nbytes = 0;
663 size_t msglen = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200664
Christopher Faulet147b8c92021-04-10 09:00:38 +0200665 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
666
Willy Tarreau51cd5952020-06-05 12:25:38 +0200667 /* Check if the server speaks LDAP (ASN.1/BER)
668 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
669 * http://tools.ietf.org/html/rfc4511
670 */
Christopher Faulet81011212021-09-16 16:01:09 +0200671 ptr = b_head(&check->bi) + 1;
672
Willy Tarreau51cd5952020-06-05 12:25:38 +0200673 /* size of LDAPMessage */
Christopher Faulet81011212021-09-16 16:01:09 +0200674 if (*ptr & 0x80) {
675 /* For message size encoded on several bytes, we only handle
676 * size encoded on 2 or 4 bytes. There is no reason to make this
677 * part to complex because only Active Directory is known to
678 * encode BindReponse length on 4 bytes.
679 */
680 nbytes = (*ptr & 0x7f);
681 if (b_data(&check->bi) < 1 + nbytes)
682 goto too_short;
683 switch (nbytes) {
684 case 4: msglen = read_n32(ptr+1); break;
685 case 2: msglen = read_n16(ptr+1); break;
686 default:
687 status = HCHK_STATUS_L7RSP;
688 desc = ist("Not LDAPv3 protocol");
689 goto error;
690 }
691 }
692 else
693 msglen = *ptr;
694 ptr += 1 + nbytes;
695
696 if (b_data(&check->bi) < 2 + nbytes + msglen)
697 goto too_short;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200698
699 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
700 * messageID: 0x02 0x01 0x01: INTEGER 1
701 * protocolOp: 0x61: bindResponse
702 */
Christopher Faulet81011212021-09-16 16:01:09 +0200703 if (memcmp(ptr, "\x02\x01\x01\x61", 4) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200704 status = HCHK_STATUS_L7RSP;
705 desc = ist("Not LDAPv3 protocol");
706 goto error;
707 }
Christopher Faulet81011212021-09-16 16:01:09 +0200708 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200709
Christopher Faulet81011212021-09-16 16:01:09 +0200710 /* skip size of bindResponse */
711 nbytes = 0;
712 if (*ptr & 0x80)
713 nbytes = (*ptr & 0x7f);
714 ptr += 1 + nbytes;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200715
716 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
717 * ldapResult: 0x0a 0x01: ENUMERATION
718 */
Christopher Faulet81011212021-09-16 16:01:09 +0200719 if (memcmp(ptr, "\x0a\x01", 2) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200720 status = HCHK_STATUS_L7RSP;
721 desc = ist("Not LDAPv3 protocol");
722 goto error;
723 }
Christopher Faulet81011212021-09-16 16:01:09 +0200724 ptr += 2;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200725
726 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
727 * resultCode
728 */
Christopher Faulet81011212021-09-16 16:01:09 +0200729 check->code = *ptr;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200730 if (check->code) {
731 status = HCHK_STATUS_L7STS;
732 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
733 goto error;
734 }
735
736 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
737 set_server_check_status(check, status, "Success");
738
739 out:
740 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200741 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200742 return ret;
743
744 error:
745 ret = TCPCHK_EVAL_STOP;
746 msg = alloc_trash_chunk();
747 if (msg)
748 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
749 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
750 goto out;
Christopher Faulet81011212021-09-16 16:01:09 +0200751
752 too_short:
753 if (!last_read)
754 goto wait_more_data;
755 /* invalid length or truncated response */
756 status = HCHK_STATUS_L7RSP;
757 goto error;
758
759 wait_more_data:
760 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
761 ret = TCPCHK_EVAL_WAIT;
762 goto out;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200763}
764
765/* Custom tcp-check expect function to parse and validate the SPOP hello agent
766 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
767 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
768 */
769enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
770{
771 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
772 enum healthcheck_status status;
773 struct buffer *msg = NULL;
774 struct ist desc = IST_NULL;
775 unsigned int framesz;
776
Christopher Faulet147b8c92021-04-10 09:00:38 +0200777 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200778
779 memcpy(&framesz, b_head(&check->bi), 4);
780 framesz = ntohl(framesz);
781
782 if (!last_read && b_data(&check->bi) < (4+framesz))
783 goto wait_more_data;
784
785 memset(b_orig(&trash), 0, b_size(&trash));
786 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
787 status = HCHK_STATUS_L7RSP;
788 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
789 goto error;
790 }
791
792 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
793 set_server_check_status(check, status, "SPOA server is ok");
794
795 out:
796 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200797 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200798 return ret;
799
800 error:
801 ret = TCPCHK_EVAL_STOP;
802 msg = alloc_trash_chunk();
803 if (msg)
804 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
805 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
806 goto out;
807
808 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200809 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200810 ret = TCPCHK_EVAL_WAIT;
811 goto out;
812}
813
814/* Custom tcp-check expect function to parse and validate the agent-check
815 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
816 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
817 */
818enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
819{
820 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
821 enum healthcheck_status status = HCHK_STATUS_CHECKED;
822 const char *hs = NULL; /* health status */
823 const char *as = NULL; /* admin status */
824 const char *ps = NULL; /* performance status */
825 const char *cs = NULL; /* maxconn */
826 const char *err = NULL; /* first error to report */
827 const char *wrn = NULL; /* first warning to report */
828 char *cmd, *p;
829
Christopher Faulet147b8c92021-04-10 09:00:38 +0200830 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
831
Willy Tarreau51cd5952020-06-05 12:25:38 +0200832 /* We're getting an agent check response. The agent could
833 * have been disabled in the mean time with a long check
834 * still pending. It is important that we ignore the whole
835 * response.
836 */
837 if (!(check->state & CHK_ST_ENABLED))
838 goto out;
839
840 /* The agent supports strings made of a single line ended by the
841 * first CR ('\r') or LF ('\n'). This line is composed of words
842 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
843 * line may optionally contained a description of a state change
844 * after a sharp ('#'), which is only considered if a health state
845 * is announced.
846 *
847 * Words may be composed of :
848 * - a numeric weight suffixed by the percent character ('%').
849 * - a health status among "up", "down", "stopped", and "fail".
850 * - an admin status among "ready", "drain", "maint".
851 *
852 * These words may appear in any order. If multiple words of the
853 * same category appear, the last one wins.
854 */
855
856 p = b_head(&check->bi);
857 while (*p && *p != '\n' && *p != '\r')
858 p++;
859
860 if (!*p) {
861 if (!last_read)
862 goto wait_more_data;
863
864 /* at least inform the admin that the agent is mis-behaving */
865 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
866 goto out;
867 }
868
869 *p = 0;
870 cmd = b_head(&check->bi);
871
872 while (*cmd) {
873 /* look for next word */
874 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
875 cmd++;
876 continue;
877 }
878
879 if (*cmd == '#') {
880 /* this is the beginning of a health status description,
881 * skip the sharp and blanks.
882 */
883 cmd++;
884 while (*cmd == '\t' || *cmd == ' ')
885 cmd++;
886 break;
887 }
888
889 /* find the end of the word so that we have a null-terminated
890 * word between <cmd> and <p>.
891 */
892 p = cmd + 1;
893 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
894 p++;
895 if (*p)
896 *p++ = 0;
897
898 /* first, health statuses */
899 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100900 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200901 status = HCHK_STATUS_L7OKD;
902 hs = cmd;
903 }
904 else if (strcasecmp(cmd, "down") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100905 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200906 status = HCHK_STATUS_L7STS;
907 hs = cmd;
908 }
909 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100910 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200911 status = HCHK_STATUS_L7STS;
912 hs = cmd;
913 }
914 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100915 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200916 status = HCHK_STATUS_L7STS;
917 hs = cmd;
918 }
919 /* admin statuses */
920 else if (strcasecmp(cmd, "ready") == 0) {
921 as = cmd;
922 }
923 else if (strcasecmp(cmd, "drain") == 0) {
924 as = cmd;
925 }
926 else if (strcasecmp(cmd, "maint") == 0) {
927 as = cmd;
928 }
929 /* try to parse a weight here and keep the last one */
930 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
931 ps = cmd;
932 }
933 /* try to parse a maxconn here */
934 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
935 cs = cmd;
936 }
937 else {
938 /* keep a copy of the first error */
939 if (!err)
940 err = cmd;
941 }
942 /* skip to next word */
943 cmd = p;
944 }
945 /* here, cmd points either to \0 or to the beginning of a
946 * description. Skip possible leading spaces.
947 */
948 while (*cmd == ' ' || *cmd == '\n')
949 cmd++;
950
951 /* First, update the admin status so that we avoid sending other
952 * possibly useless warnings and can also update the health if
953 * present after going back up.
954 */
955 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200956 if (strcasecmp(as, "drain") == 0) {
957 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200958 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200959 }
960 else if (strcasecmp(as, "maint") == 0) {
961 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200962 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200963 }
964 else {
965 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200966 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200967 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200968 }
969
970 /* now change weights */
971 if (ps) {
972 const char *msg;
973
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500974 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200975 msg = server_parse_weight_change_request(check->server, ps);
976 if (!wrn || !*wrn)
977 wrn = msg;
978 }
979
980 if (cs) {
981 const char *msg;
982
983 cs += strlen("maxconn:");
984
Christopher Faulet147b8c92021-04-10 09:00:38 +0200985 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury Denoyellecaaafd02021-06-18 11:11:36 +0200986 /* This is safe to call server_parse_maxconn_change_request
987 * because the server lock is held during the check.
988 */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200989 msg = server_parse_maxconn_change_request(check->server, cs);
990 if (!wrn || !*wrn)
991 wrn = msg;
992 }
993
994 /* and finally health status */
995 if (hs) {
996 /* We'll report some of the warnings and errors we have
997 * here. Down reports are critical, we leave them untouched.
998 * Lack of report, or report of 'UP' leaves the room for
999 * ERR first, then WARN.
1000 */
1001 const char *msg = cmd;
1002 struct buffer *t;
1003
1004 if (!*msg || status == HCHK_STATUS_L7OKD) {
1005 if (err && *err)
1006 msg = err;
1007 else if (wrn && *wrn)
1008 msg = wrn;
1009 }
1010
1011 t = get_trash_chunk();
1012 chunk_printf(t, "via agent : %s%s%s%s",
1013 hs, *msg ? " (" : "",
1014 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001015 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001016 set_server_check_status(check, status, t->area);
1017 }
1018 else if (err && *err) {
1019 /* No status change but we'd like to report something odd.
1020 * Just report the current state and copy the message.
1021 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001022 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001023 chunk_printf(&trash, "agent reports an error : %s", err);
1024 set_server_check_status(check, status/*check->status*/, trash.area);
1025 }
1026 else if (wrn && *wrn) {
1027 /* No status change but we'd like to report something odd.
1028 * Just report the current state and copy the message.
1029 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001030 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001031 chunk_printf(&trash, "agent warns : %s", wrn);
1032 set_server_check_status(check, status/*check->status*/, trash.area);
1033 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001034 else {
1035 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001036 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001037 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001038
1039 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001040 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001041 return ret;
1042
1043 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001044 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001045 ret = TCPCHK_EVAL_WAIT;
1046 goto out;
1047}
1048
1049/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1050 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1051 * TCPCHK_EVAL_STOP if an error occurred.
1052 */
1053enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1054{
1055 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1056 struct tcpcheck_connect *connect = &rule->connect;
1057 struct proxy *proxy = check->proxy;
1058 struct server *s = check->server;
1059 struct task *t = check->task;
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001060 struct conn_stream *cs = check->cs;
1061 struct connection *conn = cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001062 struct protocol *proto;
1063 struct xprt_ops *xprt;
1064 struct tcpcheck_rule *next;
1065 int status, port;
1066
Christopher Faulet147b8c92021-04-10 09:00:38 +02001067 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1068
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001069 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1070
1071 /* current connection already created, check if it is established or not */
1072 if (conn) {
1073 if (conn->flags & CO_FL_WAIT_XPRT) {
1074 /* We are still waiting for the connection establishment */
1075 if (next && next->action == TCPCHK_ACT_SEND) {
1076 if (!(check->wait_list.events & SUB_RETRY_SEND))
1077 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1078 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001079 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001080 }
1081 else
1082 ret = tcpcheck_eval_recv(check, rule);
1083 }
1084 goto out;
1085 }
1086
1087 /* Note: here check->cs = cs = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001088
Christopher Fauletb381a502020-11-25 13:47:00 +01001089 /* Always release input and output buffer when a new connect is evaluated */
1090 check_release_buf(check, &check->bi);
1091 check_release_buf(check, &check->bo);
1092
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001093 /* No connection, prepare a new one */
Christopher Faulet236c93b2020-07-02 09:19:54 +02001094 cs = cs_new(NULL, (s ? &s->obj_type : &proxy->obj_type));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001095 if (!cs) {
1096 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1097 tcpcheck_get_step_id(check, rule));
1098 if (rule->comment)
1099 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1100 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1101 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001102 TRACE_ERROR("conn-stream allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001103 goto out;
1104 }
1105
Willy Tarreau51cd5952020-06-05 12:25:38 +02001106 tasklet_set_tid(check->wait_list.tasklet, tid);
1107
1108 check->cs = cs;
1109 conn = cs->conn;
1110 conn_set_owner(conn, check->sess, NULL);
1111
1112 /* Maybe there were an older connection we were waiting on */
1113 check->wait_list.events = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001114
1115 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001116 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001117 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001118 status = SF_ERR_RESOURCE;
1119 goto fail_check;
1120 }
1121
1122 /* connect to the connect rule addr if specified, otherwise the check
1123 * addr if specified on the server. otherwise, use the server addr (it
1124 * MUST exist at this step).
1125 */
1126 *conn->dst = (is_addr(&connect->addr)
1127 ? connect->addr
1128 : (is_addr(&check->addr) ? check->addr : s->addr));
1129 proto = protocol_by_family(conn->dst->ss_family);
1130
1131 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001132 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001133 port = connect->port;
1134 if (!port && connect->port_expr) {
1135 struct sample *smp;
1136
1137 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1138 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1139 connect->port_expr, SMP_T_SINT);
1140 if (smp)
1141 port = smp->data.u.sint;
1142 }
1143 if (!port && is_inet_addr(&connect->addr))
1144 port = get_host_port(&connect->addr);
1145 if (!port && check->port)
1146 port = check->port;
1147 if (!port && is_inet_addr(&check->addr))
1148 port = get_host_port(&check->addr);
1149 if (!port) {
1150 /* The server MUST exist here */
1151 port = s->svc_port;
1152 }
1153 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001154 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001155
1156 xprt = ((connect->options & TCPCHK_OPT_SSL)
1157 ? xprt_get(XPRT_SSL)
1158 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1159
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001160 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001161 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001162 status = SF_ERR_RESOURCE;
1163 goto fail_check;
1164 }
1165
Willy Tarreau51cd5952020-06-05 12:25:38 +02001166 cs_attach(cs, check, &check_conn_cb);
1167
Christopher Fauletf7177272020-10-02 13:41:55 +02001168 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1169 conn->send_proxy_ofs = 1;
1170 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001171 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001172 }
1173 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1174 conn->send_proxy_ofs = 1;
1175 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001176 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001177 }
1178
1179 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1180 conn->send_proxy_ofs = 1;
1181 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001182 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001183 }
1184 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1185 conn->send_proxy_ofs = 1;
1186 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001187 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001188 }
1189
Willy Tarreau51cd5952020-06-05 12:25:38 +02001190 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001191 if (proto && proto->connect) {
1192 int flags = 0;
1193
Christopher Faulet004ffe92022-08-30 10:31:15 +02001194 if (!next)
1195 flags |= CONNECT_DELACK_ALWAYS;
Christopher Faulet8af4ab82022-08-24 11:38:03 +02001196 if (connect->options & TCPCHK_OPT_HAS_DATA)
Christopher Faulet004ffe92022-08-30 10:31:15 +02001197 flags |= (CONNECT_HAS_DATA|CONNECT_DELACK_ALWAYS);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001198 status = proto->connect(conn, flags);
1199 }
1200
1201 if (status != SF_ERR_NONE)
1202 goto fail_check;
1203
Christopher Faulet21ddc742020-07-01 15:26:14 +02001204 conn_set_private(conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001205 conn->ctx = cs;
1206
Willy Tarreau51cd5952020-06-05 12:25:38 +02001207#ifdef USE_OPENSSL
1208 if (connect->sni)
1209 ssl_sock_set_servername(conn, connect->sni);
1210 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1211 ssl_sock_set_servername(conn, s->check.sni);
1212
1213 if (connect->alpn)
1214 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1215 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1216 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1217#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001218
1219 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1220 /* Some servers don't like reset on close */
Willy Tarreaub41a6e92021-04-06 17:49:19 +02001221 HA_ATOMIC_AND(&fdtab[cs->conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001222 }
1223
1224 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1225 if (xprt_add_hs(conn) < 0)
1226 status = SF_ERR_RESOURCE;
1227 }
1228
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001229 if (conn_xprt_start(conn) < 0) {
1230 status = SF_ERR_RESOURCE;
1231 goto fail_check;
1232 }
1233
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001234 /* The mux may be initialized now if there isn't server attached to the
1235 * check (email alerts) or if there is a mux proto specified or if there
1236 * is no alpn.
1237 */
1238 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1239 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1240 const struct mux_ops *mux_ops;
1241
Christopher Faulet147b8c92021-04-10 09:00:38 +02001242 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001243 if (connect->mux_proto)
1244 mux_ops = connect->mux_proto->mux;
1245 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1246 mux_ops = check->mux_proto->mux;
1247 else {
1248 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1249 ? PROTO_MODE_HTTP
1250 : PROTO_MODE_TCP);
1251
1252 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1253 }
1254 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001255 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001256 status = SF_ERR_INTERNAL;
1257 goto fail_check;
1258 }
1259 }
1260
Willy Tarreau51cd5952020-06-05 12:25:38 +02001261 fail_check:
1262 /* It can return one of :
1263 * - SF_ERR_NONE if everything's OK
1264 * - SF_ERR_SRVTO if there are no more servers
1265 * - SF_ERR_SRVCL if the connection was refused by the server
1266 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1267 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1268 * - SF_ERR_INTERNAL for any other purely internal errors
1269 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1270 * Note that we try to prevent the network stack from sending the ACK during the
1271 * connect() when a pure TCP check is used (without PROXY protocol).
1272 */
1273 switch (status) {
1274 case SF_ERR_NONE:
1275 /* we allow up to min(inter, timeout.connect) for a connection
1276 * to establish but only when timeout.check is set as it may be
1277 * to short for a full check otherwise
1278 */
1279 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1280
1281 if (proxy->timeout.check && proxy->timeout.connect) {
1282 int t_con = tick_add(now_ms, proxy->timeout.connect);
1283 t->expire = tick_first(t->expire, t_con);
1284 }
1285 break;
1286 case SF_ERR_SRVTO: /* ETIMEDOUT */
1287 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1288 case SF_ERR_PRXCOND:
1289 case SF_ERR_RESOURCE:
1290 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001291 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 +02001292 chk_report_conn_err(check, errno, 0);
1293 ret = TCPCHK_EVAL_STOP;
1294 goto out;
1295 }
1296
1297 /* don't do anything until the connection is established */
1298 if (conn->flags & CO_FL_WAIT_XPRT) {
1299 if (conn->mux) {
1300 if (next && next->action == TCPCHK_ACT_SEND)
1301 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1302 else
1303 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
1304 }
1305 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001306 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001307 goto out;
1308 }
1309
1310 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001311 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001312 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001313 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001314 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001315
1316 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1317 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1318
Christopher Faulet147b8c92021-04-10 09:00:38 +02001319 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001320 return ret;
1321}
1322
1323/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1324 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1325 * TCPCHK_EVAL_STOP if an error occurred.
1326 */
1327enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1328{
1329 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1330 struct tcpcheck_send *send = &rule->send;
1331 struct conn_stream *cs = check->cs;
1332 struct connection *conn = cs_conn(cs);
1333 struct buffer *tmp = NULL;
1334 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001335 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001336
Christopher Faulet147b8c92021-04-10 09:00:38 +02001337 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1338
Christopher Fauletb381a502020-11-25 13:47:00 +01001339 if (check->state & CHK_ST_OUT_ALLOC) {
1340 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001341 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 +01001342 goto out;
1343 }
1344
1345 if (!check_get_buf(check, &check->bo)) {
1346 check->state |= CHK_ST_OUT_ALLOC;
1347 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001348 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 +01001349 goto out;
1350 }
1351
Christopher Faulet39066c22020-11-25 13:34:51 +01001352 /* Data already pending in the output buffer, send them now */
Christopher Faulet18280ca2021-08-11 15:46:29 +02001353 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 +02001354 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 +01001355 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001356 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001357
Christopher Fauletb381a502020-11-25 13:47:00 +01001358 /* Always release input buffer when a new send is evaluated */
1359 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001360
1361 switch (send->type) {
1362 case TCPCHK_SEND_STRING:
1363 case TCPCHK_SEND_BINARY:
1364 if (istlen(send->data) >= b_size(&check->bo)) {
1365 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1366 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1367 tcpcheck_get_step_id(check, rule));
1368 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1369 ret = TCPCHK_EVAL_STOP;
1370 goto out;
1371 }
1372 b_putist(&check->bo, send->data);
1373 break;
1374 case TCPCHK_SEND_STRING_LF:
1375 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1376 if (!b_data(&check->bo))
1377 goto out;
1378 break;
1379 case TCPCHK_SEND_BINARY_LF: {
1380 int len = b_size(&check->bo);
1381
1382 tmp = alloc_trash_chunk();
1383 if (!tmp)
1384 goto error_lf;
1385 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1386 if (!b_data(tmp))
1387 goto out;
1388 tmp->area[tmp->data] = '\0';
1389 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1390 goto error_lf;
1391 check->bo.data = len;
1392 break;
1393 }
1394 case TCPCHK_SEND_HTTP: {
1395 struct htx_sl *sl;
1396 struct ist meth, uri, vsn, clen, body;
1397 unsigned int slflags = 0;
1398
1399 tmp = alloc_trash_chunk();
1400 if (!tmp)
1401 goto error_htx;
1402
1403 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1404 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1405 : http_known_methods[send->http.meth.meth]);
1406 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1407 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1408 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1409 }
1410 else
1411 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1412 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1413
1414 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1415 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1416 slflags |= HTX_SL_F_VER_11;
1417 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
Christopher Faulet173161e2023-02-28 18:44:14 +01001418 if (!(send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !isttest(send->http.body))
Willy Tarreau51cd5952020-06-05 12:25:38 +02001419 slflags |= HTX_SL_F_BODYLESS;
1420
1421 htx = htx_from_buf(&check->bo);
1422 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1423 if (!sl)
1424 goto error_htx;
1425 sl->info.req.meth = send->http.meth.meth;
1426 if (!http_update_host(htx, sl, uri))
1427 goto error_htx;
1428
1429 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1430 struct tcpcheck_http_hdr *hdr;
1431 struct ist hdr_value;
1432
1433 list_for_each_entry(hdr, &send->http.hdrs, list) {
1434 chunk_reset(tmp);
1435 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1436 if (!b_data(tmp))
1437 continue;
1438 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1439 if (!htx_add_header(htx, hdr->name, hdr_value))
1440 goto error_htx;
1441 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1442 if (!http_update_authority(htx, sl, hdr_value))
1443 goto error_htx;
1444 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001445 if (isteqi(hdr->name, ist("connection")))
1446 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001447 }
1448
1449 }
1450 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1451 chunk_reset(tmp);
1452 httpchk_build_status_header(check->server, tmp);
1453 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1454 goto error_htx;
1455 }
1456
1457
1458 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1459 chunk_reset(tmp);
1460 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1461 body = ist2(b_orig(tmp), b_data(tmp));
1462 }
1463 else
1464 body = send->http.body;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001465
Christopher Fauletf7007712023-02-28 18:51:26 +01001466 if (!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close")))
Willy Tarreau51cd5952020-06-05 12:25:38 +02001467 goto error_htx;
1468
Christopher Fauletf7007712023-02-28 18:51:26 +01001469 if ((send->http.meth.meth != HTTP_METH_OPTIONS &&
1470 send->http.meth.meth != HTTP_METH_GET &&
1471 send->http.meth.meth != HTTP_METH_HEAD &&
1472 send->http.meth.meth != HTTP_METH_DELETE) || istlen(body)) {
1473 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1474 if (!htx_add_header(htx, ist("Content-length"), clen))
1475 goto error_htx;
1476 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001477
1478 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001479 (istlen(body) && !htx_add_data_atonce(htx, body)))
1480 goto error_htx;
1481
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001482 /* no more data are expected */
1483 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001484 htx_to_buf(htx, &check->bo);
1485 break;
1486 }
1487 case TCPCHK_SEND_UNDEF:
1488 /* Should never happen. */
1489 ret = TCPCHK_EVAL_STOP;
1490 goto out;
1491 };
1492
Christopher Faulet39066c22020-11-25 13:34:51 +01001493 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001494 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001495 if (conn->mux->snd_buf(cs, &check->bo,
1496 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1497 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1498 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001499 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 +02001500 goto out;
1501 }
1502 }
Christopher Faulet18280ca2021-08-11 15:46:29 +02001503 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 +02001504 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1505 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001506 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001507 goto out;
1508 }
1509
1510 out:
1511 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001512 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1513 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001514
1515 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001516 return ret;
1517
1518 error_htx:
1519 if (htx) {
1520 htx_reset(htx);
1521 htx_to_buf(htx, &check->bo);
1522 }
1523 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1524 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001525 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 +02001526 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1527 ret = TCPCHK_EVAL_STOP;
1528 goto out;
1529
1530 error_lf:
1531 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1532 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001533 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 +02001534 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1535 ret = TCPCHK_EVAL_STOP;
1536 goto out;
1537
1538}
1539
1540/* Try to receive data before evaluating a tcp-check expect rule. Returns
1541 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1542 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1543 * TCPCHK_EVAL_STOP if an error occurred.
1544 */
1545enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1546{
1547 struct conn_stream *cs = check->cs;
1548 struct connection *conn = cs_conn(cs);
1549 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1550 size_t max, read, cur_read = 0;
1551 int is_empty;
1552 int read_poll = MAX_READ_POLL_LOOPS;
1553
Christopher Faulet147b8c92021-04-10 09:00:38 +02001554 TRACE_ENTER(CHK_EV_RX_DATA, check);
1555
1556 if (check->wait_list.events & SUB_RETRY_RECV) {
1557 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001558 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001559 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001560
1561 if (cs->flags & CS_FL_EOS)
1562 goto end_recv;
1563
Christopher Faulet147b8c92021-04-10 09:00:38 +02001564 if (check->state & CHK_ST_IN_ALLOC) {
1565 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001566 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001567 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001568
1569 if (!check_get_buf(check, &check->bi)) {
1570 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001571 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001572 goto wait_more_data;
1573 }
1574
Willy Tarreau51cd5952020-06-05 12:25:38 +02001575 /* errors on the connection and the conn-stream were already checked */
1576
1577 /* prepare to detect if the mux needs more room */
1578 cs->flags &= ~CS_FL_WANT_ROOM;
1579
1580 while ((cs->flags & CS_FL_RCV_MORE) ||
1581 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1582 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1583 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1584 cur_read += read;
1585 if (!read ||
1586 (cs->flags & CS_FL_WANT_ROOM) ||
1587 (--read_poll <= 0) ||
1588 (read < max && read >= global.tune.recv_enough))
1589 break;
1590 }
1591
1592 end_recv:
1593 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1594 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1595 /* Report network errors only if we got no other data. Otherwise
1596 * we'll let the upper layers decide whether the response is OK
1597 * or not. It is very common that an RST sent by the server is
1598 * reported as an error just after the last data chunk.
1599 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001600 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001601 goto stop;
1602 }
1603 if (!cur_read) {
Christopher Fauletbd017472021-10-20 13:53:38 +02001604 if (cs->flags & CS_FL_EOI) {
1605 /* If EOI is set, it means there is a response or an error */
1606 goto out;
1607 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001608 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1609 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001610 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001611 goto wait_more_data;
1612 }
1613 if (is_empty) {
1614 int status;
1615
1616 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1617 tcpcheck_get_step_id(check, rule));
1618 if (rule->comment)
1619 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1620
Christopher Faulet147b8c92021-04-10 09:00:38 +02001621 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001622 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1623 set_server_check_status(check, status, trash.area);
1624 goto stop;
1625 }
1626 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001627 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001628
1629 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001630 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1631 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001632
1633 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001634 return ret;
1635
1636 stop:
1637 ret = TCPCHK_EVAL_STOP;
1638 goto out;
1639
1640 wait_more_data:
1641 ret = TCPCHK_EVAL_WAIT;
1642 goto out;
1643}
1644
1645/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1646 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1647 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1648 * error occurred.
1649 */
1650enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1651{
1652 struct htx *htx = htxbuf(&check->bi);
1653 struct htx_sl *sl;
1654 struct htx_blk *blk;
1655 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1656 struct tcpcheck_expect *expect = &rule->expect;
1657 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1658 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1659 struct ist desc = IST_NULL;
1660 int i, match, inverse;
1661
Christopher Faulet147b8c92021-04-10 09:00:38 +02001662 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1663
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001664 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001665
1666 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001667 TRACE_ERROR("invalid response", 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 }
1671
1672 if (htx_is_empty(htx)) {
1673 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001674 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001675 status = HCHK_STATUS_L7RSP;
1676 goto error;
1677 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001678 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001679 goto wait_more_data;
1680 }
1681
1682 sl = http_get_stline(htx);
1683 check->code = sl->info.res.status;
1684
1685 if (check->server &&
1686 (check->server->proxy->options & PR_O_DISABLE404) &&
1687 (check->server->next_state != SRV_ST_STOPPED) &&
1688 (check->code == 404)) {
1689 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001690 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001691 goto out;
1692 }
1693
1694 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1695 /* Make GCC happy ; initialize match to a failure state. */
1696 match = inverse;
1697 status = expect->err_status;
1698
1699 switch (expect->type) {
1700 case TCPCHK_EXPECT_HTTP_STATUS:
1701 match = 0;
1702 for (i = 0; i < expect->codes.num; i++) {
1703 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1704 sl->info.res.status <= expect->codes.codes[i][1]) {
1705 match = 1;
1706 break;
1707 }
1708 }
1709
1710 /* Set status and description in case of error */
1711 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1712 if (LIST_ISEMPTY(&expect->onerror_fmt))
1713 desc = htx_sl_res_reason(sl);
1714 break;
1715 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1716 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1717
1718 /* Set status and description in case of error */
1719 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1720 if (LIST_ISEMPTY(&expect->onerror_fmt))
1721 desc = htx_sl_res_reason(sl);
1722 break;
1723
1724 case TCPCHK_EXPECT_HTTP_HEADER: {
1725 struct http_hdr_ctx ctx;
1726 struct ist npat, vpat, value;
1727 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1728
1729 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1730 nbuf = alloc_trash_chunk();
1731 if (!nbuf) {
1732 status = HCHK_STATUS_L7RSP;
1733 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001734 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001735 goto error;
1736 }
1737 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1738 if (!b_data(nbuf)) {
1739 status = HCHK_STATUS_L7RSP;
1740 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001741 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001742 goto error;
1743 }
1744 npat = ist2(b_orig(nbuf), b_data(nbuf));
1745 }
1746 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1747 npat = expect->hdr.name;
1748
1749 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1750 vbuf = alloc_trash_chunk();
1751 if (!vbuf) {
1752 status = HCHK_STATUS_L7RSP;
1753 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001754 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001755 goto error;
1756 }
1757 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1758 if (!b_data(vbuf)) {
1759 status = HCHK_STATUS_L7RSP;
1760 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001761 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001762 goto error;
1763 }
1764 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1765 }
1766 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1767 vpat = expect->hdr.value;
1768
1769 match = 0;
1770 ctx.blk = NULL;
1771 while (1) {
1772 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1773 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1774 if (!http_find_str_header(htx, npat, &ctx, full))
1775 goto end_of_match;
1776 break;
1777 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1778 if (!http_find_pfx_header(htx, npat, &ctx, full))
1779 goto end_of_match;
1780 break;
1781 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1782 if (!http_find_sfx_header(htx, npat, &ctx, full))
1783 goto end_of_match;
1784 break;
1785 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1786 if (!http_find_sub_header(htx, npat, &ctx, full))
1787 goto end_of_match;
1788 break;
1789 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1790 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1791 goto end_of_match;
1792 break;
1793 default:
1794 /* should never happen */
1795 goto end_of_match;
1796 }
1797
1798 /* A header has matched the name pattern, let's test its
1799 * value now (always defined from there). If there is no
1800 * value pattern, it is a good match.
1801 */
1802
1803 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1804 match = 1;
1805 goto end_of_match;
1806 }
1807
1808 value = ctx.value;
1809 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1810 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1811 if (isteq(value, vpat)) {
1812 match = 1;
1813 goto end_of_match;
1814 }
1815 break;
1816 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1817 if (istlen(value) < istlen(vpat))
1818 break;
1819 value = ist2(istptr(value), istlen(vpat));
1820 if (isteq(value, vpat)) {
1821 match = 1;
1822 goto end_of_match;
1823 }
1824 break;
1825 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1826 if (istlen(value) < istlen(vpat))
1827 break;
1828 value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
1829 if (isteq(value, vpat)) {
1830 match = 1;
1831 goto end_of_match;
1832 }
1833 break;
1834 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1835 if (isttest(istist(value, vpat))) {
1836 match = 1;
1837 goto end_of_match;
1838 }
1839 break;
1840 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1841 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1842 match = 1;
1843 goto end_of_match;
1844 }
1845 break;
1846 }
1847 }
1848
1849 end_of_match:
1850 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1851 if (LIST_ISEMPTY(&expect->onerror_fmt))
1852 desc = htx_sl_res_reason(sl);
1853 break;
1854 }
1855
1856 case TCPCHK_EXPECT_HTTP_BODY:
1857 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1858 case TCPCHK_EXPECT_HTTP_BODY_LF:
1859 match = 0;
1860 chunk_reset(&trash);
1861 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1862 enum htx_blk_type type = htx_get_blk_type(blk);
1863
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001864 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001865 break;
1866 if (type == HTX_BLK_DATA) {
1867 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1868 break;
1869 }
1870 }
1871
1872 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001873 if (!last_read) {
1874 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001875 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001876 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001877 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1878 if (LIST_ISEMPTY(&expect->onerror_fmt))
1879 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001880 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001881 goto error;
1882 }
1883
1884 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1885 tmp = alloc_trash_chunk();
1886 if (!tmp) {
1887 status = HCHK_STATUS_L7RSP;
1888 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001889 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001890 goto error;
1891 }
1892 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1893 if (!b_data(tmp)) {
1894 status = HCHK_STATUS_L7RSP;
1895 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001896 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001897 goto error;
1898 }
1899 }
1900
1901 if (!last_read &&
1902 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1903 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1904 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1905 ret = TCPCHK_EVAL_WAIT;
1906 goto out;
1907 }
1908
1909 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1910 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1911 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1912 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1913 else
1914 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1915
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001916 /* Wait for more data on mismatch only if no minimum is defined (-1),
1917 * otherwise the absence of match is already conclusive.
1918 */
1919 if (!match && !last_read && (expect->min_recv == -1)) {
1920 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001921 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001922 goto out;
1923 }
1924
Willy Tarreau51cd5952020-06-05 12:25:38 +02001925 /* Set status and description in case of error */
1926 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1927 if (LIST_ISEMPTY(&expect->onerror_fmt))
1928 desc = (inverse
1929 ? ist("HTTP check matched unwanted content")
1930 : ist("HTTP content check did not match"));
1931 break;
1932
1933
1934 default:
1935 /* should never happen */
1936 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1937 goto error;
1938 }
1939
Christopher Faulet147b8c92021-04-10 09:00:38 +02001940 if (!(match ^ inverse)) {
1941 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001942 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001943 }
1944
1945 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001946
1947 out:
1948 free_trash_chunk(tmp);
1949 free_trash_chunk(nbuf);
1950 free_trash_chunk(vbuf);
1951 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001952 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001953 return ret;
1954
1955 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001956 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001957 ret = TCPCHK_EVAL_STOP;
1958 msg = alloc_trash_chunk();
1959 if (msg)
1960 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1961 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1962 goto out;
1963
1964 wait_more_data:
1965 ret = TCPCHK_EVAL_WAIT;
1966 goto out;
1967}
1968
1969/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1970 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1971 * if an error occurred.
1972 */
1973enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1974{
1975 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1976 struct tcpcheck_expect *expect = &rule->expect;
1977 struct buffer *msg = NULL, *tmp = NULL;
1978 struct ist desc = IST_NULL;
1979 enum healthcheck_status status;
1980 int match, inverse;
1981
Christopher Faulet147b8c92021-04-10 09:00:38 +02001982 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1983
Willy Tarreau51cd5952020-06-05 12:25:38 +02001984 last_read |= b_full(&check->bi);
1985
1986 /* The current expect might need more data than the previous one, check again
1987 * that the minimum amount data required to match is respected.
1988 */
1989 if (!last_read) {
1990 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1991 (b_data(&check->bi) < istlen(expect->data))) {
1992 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001993 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001994 goto out;
1995 }
1996 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1997 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001998 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001999 goto out;
2000 }
2001 }
2002
2003 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2004 /* Make GCC happy ; initialize match to a failure state. */
2005 match = inverse;
2006 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
2007
2008 switch (expect->type) {
2009 case TCPCHK_EXPECT_STRING:
2010 case TCPCHK_EXPECT_BINARY:
2011 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2012 break;
2013 case TCPCHK_EXPECT_STRING_REGEX:
2014 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2015 break;
2016
2017 case TCPCHK_EXPECT_BINARY_REGEX:
2018 chunk_reset(&trash);
2019 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2020 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2021 break;
2022
2023 case TCPCHK_EXPECT_STRING_LF:
2024 case TCPCHK_EXPECT_BINARY_LF:
2025 match = 0;
2026 tmp = alloc_trash_chunk();
2027 if (!tmp) {
2028 status = HCHK_STATUS_L7RSP;
2029 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002030 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002031 goto error;
2032 }
2033 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2034 if (!b_data(tmp)) {
2035 status = HCHK_STATUS_L7RSP;
2036 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002037 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002038 goto error;
2039 }
2040 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2041 int len = tmp->data;
2042 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2043 status = HCHK_STATUS_L7RSP;
2044 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002045 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002046 goto error;
2047 }
2048 tmp->data = len;
2049 }
2050 if (b_data(&check->bi) < tmp->data) {
2051 if (!last_read) {
2052 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002053 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002054 goto out;
2055 }
2056 break;
2057 }
2058 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2059 break;
2060
2061 case TCPCHK_EXPECT_CUSTOM:
2062 if (expect->custom)
2063 ret = expect->custom(check, rule, last_read);
2064 goto out;
2065 default:
2066 /* Should never happen. */
2067 ret = TCPCHK_EVAL_STOP;
2068 goto out;
2069 }
2070
2071
2072 /* Wait for more data on mismatch only if no minimum is defined (-1),
2073 * otherwise the absence of match is already conclusive.
2074 */
2075 if (!match && !last_read && (expect->min_recv == -1)) {
2076 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002077 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002078 goto out;
2079 }
2080
2081 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002082 if (match ^ inverse) {
2083 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002084 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002085 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002086
2087 error:
2088 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002089 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002090 ret = TCPCHK_EVAL_STOP;
2091 msg = alloc_trash_chunk();
2092 if (msg)
2093 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2094 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2095 free_trash_chunk(msg);
2096
2097 out:
2098 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002099 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002100 return ret;
2101}
2102
2103/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2104 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2105 * waits.
2106 */
2107enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2108{
2109 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2110 struct act_rule *act_rule;
2111 enum act_return act_ret;
2112
2113 act_rule =rule->action_kw.rule;
2114 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2115 if (act_ret != ACT_RET_CONT) {
2116 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2117 tcpcheck_get_step_id(check, rule));
2118 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2119 ret = TCPCHK_EVAL_STOP;
2120 }
2121
2122 return ret;
2123}
2124
2125/* Executes a tcp-check ruleset. Note that this is called both from the
2126 * connection's wake() callback and from the check scheduling task. It returns
2127 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2128 * presenting the risk of an fd replacement.
2129 *
2130 * Please do NOT place any return statement in this function and only leave
2131 * via the out_end_tcpcheck label after setting retcode.
2132 */
2133int tcpcheck_main(struct check *check)
2134{
2135 struct tcpcheck_rule *rule;
2136 struct conn_stream *cs = check->cs;
2137 struct connection *conn = cs_conn(cs);
2138 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002139 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002140 enum tcpcheck_eval_ret eval_ret;
2141
2142 /* here, we know that the check is complete or that it failed */
2143 if (check->result != CHK_RES_UNKNOWN)
2144 goto out;
2145
Christopher Faulet147b8c92021-04-10 09:00:38 +02002146 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2147
Willy Tarreau51cd5952020-06-05 12:25:38 +02002148 /* Note: the conn-stream and the connection may only be undefined before
2149 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002150 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002151 */
2152
2153 /* 1- check for connection error, if any */
2154 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2155 goto out_end_tcpcheck;
2156
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002157 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002158 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002159 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002160 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002161 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2162 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002163
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002164 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002165 * tcp-check variables */
2166 else {
2167 struct tcpcheck_var *var;
2168
2169 /* First evaluation, create a session */
2170 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2171 if (!check->sess) {
2172 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002173 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002174 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2175 goto out_end_tcpcheck;
2176 }
2177 vars_init(&check->vars, SCOPE_CHECK);
2178 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2179
2180 /* Preset tcp-check variables */
2181 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2182 struct sample smp;
2183
2184 memset(&smp, 0, sizeof(smp));
2185 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2186 smp.data = var->data;
2187 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2188 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002189 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002190 }
2191
2192 /* Now evaluate the tcp-check rules */
2193
2194 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2195 check->code = 0;
2196 switch (rule->action) {
2197 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002198 /* Not the first connection, release it first */
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002199 if (cs && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002200 check->state |= CHK_ST_CLOSE_CONN;
2201 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002202 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002203
2204 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002205
2206 /* We are still waiting the connection gets closed */
2207 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002208 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002209 eval_ret = TCPCHK_EVAL_WAIT;
2210 break;
2211 }
2212
Christopher Faulet147b8c92021-04-10 09:00:38 +02002213 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002214 eval_ret = tcpcheck_eval_connect(check, rule);
2215
2216 /* Refresh conn-stream and connection */
2217 cs = check->cs;
2218 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002219 last_read = 0;
2220 must_read = ((cs && IS_HTX_CS(cs)) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002221 break;
2222 case TCPCHK_ACT_SEND:
2223 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002224 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002225 eval_ret = tcpcheck_eval_send(check, rule);
2226 must_read = 1;
2227 break;
2228 case TCPCHK_ACT_EXPECT:
2229 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002230 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002231 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002232 eval_ret = tcpcheck_eval_recv(check, rule);
2233 if (eval_ret == TCPCHK_EVAL_STOP)
2234 goto out_end_tcpcheck;
2235 else if (eval_ret == TCPCHK_EVAL_WAIT)
2236 goto out;
2237 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2238 must_read = 0;
2239 }
2240
2241 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2242 ? tcpcheck_eval_expect_http(check, rule, last_read)
2243 : tcpcheck_eval_expect(check, rule, last_read));
2244
2245 if (eval_ret == TCPCHK_EVAL_WAIT) {
2246 check->current_step = rule->expect.head;
2247 if (!(check->wait_list.events & SUB_RETRY_RECV))
2248 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2249 }
2250 break;
2251 case TCPCHK_ACT_ACTION_KW:
2252 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002253 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002254 eval_ret = tcpcheck_eval_action_kw(check, rule);
2255 break;
2256 default:
2257 /* Otherwise, just go to the next one and don't update
2258 * the current step
2259 */
2260 eval_ret = TCPCHK_EVAL_CONTINUE;
2261 break;
2262 }
2263
2264 switch (eval_ret) {
2265 case TCPCHK_EVAL_CONTINUE:
2266 break;
2267 case TCPCHK_EVAL_WAIT:
2268 goto out;
2269 case TCPCHK_EVAL_STOP:
2270 goto out_end_tcpcheck;
2271 }
2272 }
2273
2274 /* All rules was evaluated */
2275 if (check->current_step) {
2276 rule = check->current_step;
2277
Christopher Faulet147b8c92021-04-10 09:00:38 +02002278 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2279
Willy Tarreau51cd5952020-06-05 12:25:38 +02002280 if (rule->action == TCPCHK_ACT_EXPECT) {
2281 struct buffer *msg;
2282 enum healthcheck_status status;
2283
2284 if (check->server &&
2285 (check->server->proxy->options & PR_O_DISABLE404) &&
2286 (check->server->next_state != SRV_ST_STOPPED) &&
2287 (check->code == 404)) {
2288 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002289 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002290 goto out_end_tcpcheck;
2291 }
2292
2293 msg = alloc_trash_chunk();
2294 if (msg)
2295 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2296 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2297 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2298 free_trash_chunk(msg);
2299 }
2300 else if (rule->action == TCPCHK_ACT_CONNECT) {
2301 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2302 enum healthcheck_status status = HCHK_STATUS_L4OK;
2303#ifdef USE_OPENSSL
2304 if (ssl_sock_is_ssl(conn))
2305 status = HCHK_STATUS_L6OK;
2306#endif
2307 set_server_check_status(check, status, msg);
2308 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002309 else
2310 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002311 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002312 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002313 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002314 }
2315 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002316
2317 out_end_tcpcheck:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002318 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) {
2319 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002320 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002321 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002322
Christopher Fauletb381a502020-11-25 13:47:00 +01002323 /* the tcpcheck is finished, release in/out buffer now */
2324 check_release_buf(check, &check->bi);
2325 check_release_buf(check, &check->bo);
2326
Willy Tarreau51cd5952020-06-05 12:25:38 +02002327 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002328 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002329 return retcode;
2330}
2331
2332
2333/**************************************************************************/
2334/******************* Internals to parse tcp-check rules *******************/
2335/**************************************************************************/
2336struct action_kw_list tcp_check_keywords = {
2337 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2338};
2339
2340/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2341 * returned on error.
2342 */
2343struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2344 struct list *rules, struct action_kw *kw,
2345 const char *file, int line, char **errmsg)
2346{
2347 struct tcpcheck_rule *chk = NULL;
2348 struct act_rule *actrule = NULL;
2349
2350 actrule = calloc(1, sizeof(*actrule));
2351 if (!actrule) {
2352 memprintf(errmsg, "out of memory");
2353 goto error;
2354 }
2355 actrule->kw = kw;
2356 actrule->from = ACT_F_TCP_CHK;
2357
2358 cur_arg++;
2359 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2360 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2361 goto error;
2362 }
2363
2364 chk = calloc(1, sizeof(*chk));
2365 if (!chk) {
2366 memprintf(errmsg, "out of memory");
2367 goto error;
2368 }
2369 chk->action = TCPCHK_ACT_ACTION_KW;
2370 chk->action_kw.rule = actrule;
2371 return chk;
2372
2373 error:
2374 free(actrule);
2375 return NULL;
2376}
2377
2378/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2379 * returned on error.
2380 */
2381struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2382 const char *file, int line, char **errmsg)
2383{
2384 struct tcpcheck_rule *chk = NULL;
2385 struct sockaddr_storage *sk = NULL;
2386 char *comment = NULL, *sni = NULL, *alpn = NULL;
2387 struct sample_expr *port_expr = NULL;
2388 const struct mux_proto_list *mux_proto = NULL;
2389 unsigned short conn_opts = 0;
2390 long port = 0;
2391 int alpn_len = 0;
2392
2393 list_for_each_entry(chk, rules, list) {
2394 if (chk->action == TCPCHK_ACT_CONNECT)
2395 break;
2396 if (chk->action == TCPCHK_ACT_COMMENT ||
2397 chk->action == TCPCHK_ACT_ACTION_KW ||
2398 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2399 continue;
2400
2401 memprintf(errmsg, "first step MUST also be a 'connect', "
2402 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2403 "when there is a 'connect' step in the tcp-check ruleset");
2404 goto error;
2405 }
2406
2407 cur_arg++;
2408 while (*(args[cur_arg])) {
2409 if (strcmp(args[cur_arg], "default") == 0)
2410 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2411 else if (strcmp(args[cur_arg], "addr") == 0) {
2412 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002413
2414 if (!*(args[cur_arg+1])) {
2415 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2416 goto error;
2417 }
2418
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002419 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2420 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002421 if (!sk) {
2422 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2423 goto error;
2424 }
2425
Willy Tarreau51cd5952020-06-05 12:25:38 +02002426 cur_arg++;
2427 }
2428 else if (strcmp(args[cur_arg], "port") == 0) {
2429 const char *p, *end;
2430
2431 if (!*(args[cur_arg+1])) {
2432 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2433 goto error;
2434 }
2435 cur_arg++;
2436
2437 port = 0;
2438 release_sample_expr(port_expr);
2439 p = args[cur_arg]; end = p + strlen(p);
2440 port = read_uint(&p, end);
2441 if (p != end) {
2442 int idx = 0;
2443
2444 px->conf.args.ctx = ARGC_SRV;
2445 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet8551c342021-09-30 16:22:51 +02002446 file, line, errmsg, (px->cap & PR_CAP_DEF) ? NULL: &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002447
2448 if (!port_expr) {
2449 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2450 goto error;
2451 }
2452 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2453 memprintf(errmsg, "error detected while parsing port expression : "
2454 " fetch method '%s' extracts information from '%s', "
2455 "none of which is available here.\n",
2456 args[cur_arg], sample_src_names(port_expr->fetch->use));
2457 goto error;
2458 }
2459 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2460 }
2461 else if (port > 65535 || port < 1) {
2462 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2463 args[cur_arg]);
2464 goto error;
2465 }
2466 }
2467 else if (strcmp(args[cur_arg], "proto") == 0) {
2468 if (!*(args[cur_arg+1])) {
2469 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2470 goto error;
2471 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002472 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002473 if (!mux_proto) {
2474 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2475 goto error;
2476 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002477
2478 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2479 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2480 goto error;
2481 }
2482 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2483 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2484 goto error;
2485 }
2486
Willy Tarreau51cd5952020-06-05 12:25:38 +02002487 cur_arg++;
2488 }
2489 else if (strcmp(args[cur_arg], "comment") == 0) {
2490 if (!*(args[cur_arg+1])) {
2491 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2492 goto error;
2493 }
2494 cur_arg++;
2495 free(comment);
2496 comment = strdup(args[cur_arg]);
2497 if (!comment) {
2498 memprintf(errmsg, "out of memory");
2499 goto error;
2500 }
2501 }
2502 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2503 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2504 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2505 conn_opts |= TCPCHK_OPT_SOCKS4;
2506 else if (strcmp(args[cur_arg], "linger") == 0)
2507 conn_opts |= TCPCHK_OPT_LINGER;
2508#ifdef USE_OPENSSL
2509 else if (strcmp(args[cur_arg], "ssl") == 0) {
2510 px->options |= PR_O_TCPCHK_SSL;
2511 conn_opts |= TCPCHK_OPT_SSL;
2512 }
2513 else if (strcmp(args[cur_arg], "sni") == 0) {
2514 if (!*(args[cur_arg+1])) {
2515 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2516 goto error;
2517 }
2518 cur_arg++;
2519 free(sni);
2520 sni = strdup(args[cur_arg]);
2521 if (!sni) {
2522 memprintf(errmsg, "out of memory");
2523 goto error;
2524 }
2525 }
2526 else if (strcmp(args[cur_arg], "alpn") == 0) {
2527#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2528 free(alpn);
2529 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2530 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2531 goto error;
2532 }
2533 cur_arg++;
2534#else
2535 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2536 goto error;
2537#endif
2538 }
2539#endif /* USE_OPENSSL */
2540
2541 else {
2542 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2543#ifdef USE_OPENSSL
2544 ", 'ssl', 'sni', 'alpn'"
2545#endif /* USE_OPENSSL */
2546 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2547 args[cur_arg]);
2548 goto error;
2549 }
2550 cur_arg++;
2551 }
2552
2553 chk = calloc(1, sizeof(*chk));
2554 if (!chk) {
2555 memprintf(errmsg, "out of memory");
2556 goto error;
2557 }
2558 chk->action = TCPCHK_ACT_CONNECT;
2559 chk->comment = comment;
2560 chk->connect.port = port;
2561 chk->connect.options = conn_opts;
2562 chk->connect.sni = sni;
2563 chk->connect.alpn = alpn;
2564 chk->connect.alpn_len= alpn_len;
2565 chk->connect.port_expr= port_expr;
2566 chk->connect.mux_proto= mux_proto;
2567 if (sk)
2568 chk->connect.addr = *sk;
2569 return chk;
2570
2571 error:
2572 free(alpn);
2573 free(sni);
2574 free(comment);
2575 release_sample_expr(port_expr);
2576 return NULL;
2577}
2578
2579/* Parses and creates a tcp-check send rule. NULL is returned on error */
2580struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2581 const char *file, int line, char **errmsg)
2582{
2583 struct tcpcheck_rule *chk = NULL;
2584 char *comment = NULL, *data = NULL;
2585 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2586
2587 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2588 type = TCPCHK_SEND_BINARY_LF;
2589 else if (strcmp(args[cur_arg], "send-binary") == 0)
2590 type = TCPCHK_SEND_BINARY;
2591 else if (strcmp(args[cur_arg], "send-lf") == 0)
2592 type = TCPCHK_SEND_STRING_LF;
2593 else if (strcmp(args[cur_arg], "send") == 0)
2594 type = TCPCHK_SEND_STRING;
2595
2596 if (!*(args[cur_arg+1])) {
2597 memprintf(errmsg, "'%s' expects a %s as argument",
2598 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2599 goto error;
2600 }
2601
2602 data = args[cur_arg+1];
2603
2604 cur_arg += 2;
2605 while (*(args[cur_arg])) {
2606 if (strcmp(args[cur_arg], "comment") == 0) {
2607 if (!*(args[cur_arg+1])) {
2608 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2609 goto error;
2610 }
2611 cur_arg++;
2612 free(comment);
2613 comment = strdup(args[cur_arg]);
2614 if (!comment) {
2615 memprintf(errmsg, "out of memory");
2616 goto error;
2617 }
2618 }
2619 else {
2620 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2621 args[cur_arg]);
2622 goto error;
2623 }
2624 cur_arg++;
2625 }
2626
2627 chk = calloc(1, sizeof(*chk));
2628 if (!chk) {
2629 memprintf(errmsg, "out of memory");
2630 goto error;
2631 }
2632 chk->action = TCPCHK_ACT_SEND;
2633 chk->comment = comment;
2634 chk->send.type = type;
2635
2636 switch (chk->send.type) {
2637 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002638 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002639 if (!isttest(chk->send.data)) {
2640 memprintf(errmsg, "out of memory");
2641 goto error;
2642 }
2643 break;
2644 case TCPCHK_SEND_BINARY: {
2645 int len = chk->send.data.len;
2646 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2647 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2648 goto error;
2649 }
2650 chk->send.data.len = len;
2651 break;
2652 }
2653 case TCPCHK_SEND_STRING_LF:
2654 case TCPCHK_SEND_BINARY_LF:
2655 LIST_INIT(&chk->send.fmt);
2656 px->conf.args.ctx = ARGC_SRV;
2657 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2658 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2659 goto error;
2660 }
2661 break;
2662 case TCPCHK_SEND_HTTP:
2663 case TCPCHK_SEND_UNDEF:
2664 goto error;
2665 }
2666
2667 return chk;
2668
2669 error:
2670 free(chk);
2671 free(comment);
2672 return NULL;
2673}
2674
2675/* Parses and creates a http-check send rule. NULL is returned on error */
2676struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2677 const char *file, int line, char **errmsg)
2678{
2679 struct tcpcheck_rule *chk = NULL;
2680 struct tcpcheck_http_hdr *hdr = NULL;
2681 struct http_hdr hdrs[global.tune.max_http_hdr];
2682 char *meth = NULL, *uri = NULL, *vsn = NULL;
2683 char *body = NULL, *comment = NULL;
2684 unsigned int flags = 0;
2685 int i = 0, host_hdr = -1;
2686
2687 cur_arg++;
2688 while (*(args[cur_arg])) {
2689 if (strcmp(args[cur_arg], "meth") == 0) {
2690 if (!*(args[cur_arg+1])) {
2691 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2692 goto error;
2693 }
2694 cur_arg++;
2695 meth = args[cur_arg];
2696 }
2697 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2698 if (!*(args[cur_arg+1])) {
2699 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2700 goto error;
2701 }
2702 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2703 if (strcmp(args[cur_arg], "uri-lf") == 0)
2704 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2705 cur_arg++;
2706 uri = args[cur_arg];
2707 }
2708 else if (strcmp(args[cur_arg], "ver") == 0) {
2709 if (!*(args[cur_arg+1])) {
2710 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2711 goto error;
2712 }
2713 cur_arg++;
2714 vsn = args[cur_arg];
2715 }
2716 else if (strcmp(args[cur_arg], "hdr") == 0) {
2717 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2718 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2719 goto error;
2720 }
2721
2722 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2723 if (host_hdr >= 0) {
2724 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2725 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2726 goto error;
2727 }
2728 host_hdr = i;
2729 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002730 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002731 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2732 goto skip_hdr;
2733
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002734 hdrs[i].n = ist(args[cur_arg + 1]);
2735 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002736 i++;
2737 skip_hdr:
2738 cur_arg += 2;
2739 }
2740 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2741 if (!*(args[cur_arg+1])) {
2742 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2743 goto error;
2744 }
2745 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2746 if (strcmp(args[cur_arg], "body-lf") == 0)
2747 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2748 cur_arg++;
2749 body = args[cur_arg];
2750 }
2751 else if (strcmp(args[cur_arg], "comment") == 0) {
2752 if (!*(args[cur_arg+1])) {
2753 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2754 goto error;
2755 }
2756 cur_arg++;
2757 free(comment);
2758 comment = strdup(args[cur_arg]);
2759 if (!comment) {
2760 memprintf(errmsg, "out of memory");
2761 goto error;
2762 }
2763 }
2764 else {
2765 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2766 " but got '%s' as argument.", args[cur_arg]);
2767 goto error;
2768 }
2769 cur_arg++;
2770 }
2771
2772 hdrs[i].n = hdrs[i].v = IST_NULL;
2773
2774 chk = calloc(1, sizeof(*chk));
2775 if (!chk) {
2776 memprintf(errmsg, "out of memory");
2777 goto error;
2778 }
2779 chk->action = TCPCHK_ACT_SEND;
2780 chk->comment = comment; comment = NULL;
2781 chk->send.type = TCPCHK_SEND_HTTP;
2782 chk->send.http.flags = flags;
2783 LIST_INIT(&chk->send.http.hdrs);
2784
2785 if (meth) {
2786 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2787 chk->send.http.meth.str.area = strdup(meth);
2788 chk->send.http.meth.str.data = strlen(meth);
2789 if (!chk->send.http.meth.str.area) {
2790 memprintf(errmsg, "out of memory");
2791 goto error;
2792 }
2793 }
2794 if (uri) {
2795 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2796 LIST_INIT(&chk->send.http.uri_fmt);
2797 px->conf.args.ctx = ARGC_SRV;
2798 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2799 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2800 goto error;
2801 }
2802 }
2803 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002804 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002805 if (!isttest(chk->send.http.uri)) {
2806 memprintf(errmsg, "out of memory");
2807 goto error;
2808 }
2809 }
2810 }
2811 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002812 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002813 if (!isttest(chk->send.http.vsn)) {
2814 memprintf(errmsg, "out of memory");
2815 goto error;
2816 }
2817 }
2818 for (i = 0; istlen(hdrs[i].n); i++) {
2819 hdr = calloc(1, sizeof(*hdr));
2820 if (!hdr) {
2821 memprintf(errmsg, "out of memory");
2822 goto error;
2823 }
2824 LIST_INIT(&hdr->value);
2825 hdr->name = istdup(hdrs[i].n);
2826 if (!isttest(hdr->name)) {
2827 memprintf(errmsg, "out of memory");
2828 goto error;
2829 }
2830
2831 ist0(hdrs[i].v);
2832 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2833 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002834 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002835 hdr = NULL;
2836 }
2837
2838 if (body) {
2839 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2840 LIST_INIT(&chk->send.http.body_fmt);
2841 px->conf.args.ctx = ARGC_SRV;
2842 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2843 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2844 goto error;
2845 }
2846 }
2847 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002848 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002849 if (!isttest(chk->send.http.body)) {
2850 memprintf(errmsg, "out of memory");
2851 goto error;
2852 }
2853 }
2854 }
2855
2856 return chk;
2857
2858 error:
2859 free_tcpcheck_http_hdr(hdr);
2860 free_tcpcheck(chk, 0);
2861 free(comment);
2862 return NULL;
2863}
2864
2865/* Parses and creates a http-check comment rule. NULL is returned on error */
2866struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2867 const char *file, int line, char **errmsg)
2868{
2869 struct tcpcheck_rule *chk = NULL;
2870 char *comment = NULL;
2871
2872 if (!*(args[cur_arg+1])) {
2873 memprintf(errmsg, "expects a string as argument");
2874 goto error;
2875 }
2876 cur_arg++;
2877 comment = strdup(args[cur_arg]);
2878 if (!comment) {
2879 memprintf(errmsg, "out of memory");
2880 goto error;
2881 }
2882
2883 chk = calloc(1, sizeof(*chk));
2884 if (!chk) {
2885 memprintf(errmsg, "out of memory");
2886 goto error;
2887 }
2888 chk->action = TCPCHK_ACT_COMMENT;
2889 chk->comment = comment;
2890 return chk;
2891
2892 error:
2893 free(comment);
2894 return NULL;
2895}
2896
2897/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2898 * on error. <proto> is set to the right protocol flags (covered by the
2899 * TCPCHK_RULES_PROTO_CHK mask).
2900 */
2901struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2902 struct list *rules, unsigned int proto,
2903 const char *file, int line, char **errmsg)
2904{
2905 struct tcpcheck_rule *prev_check, *chk = NULL;
2906 struct sample_expr *status_expr = NULL;
2907 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2908 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2909 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2910 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2911 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2912 unsigned int flags = 0;
2913 long min_recv = -1;
2914 int inverse = 0;
2915
2916 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2917 if (!*(args[cur_arg+1])) {
2918 memprintf(errmsg, "expects at least a matching pattern as arguments");
2919 goto error;
2920 }
2921
2922 cur_arg++;
2923 while (*(args[cur_arg])) {
2924 int in_pattern = 0;
2925
2926 rescan:
2927 if (strcmp(args[cur_arg], "min-recv") == 0) {
2928 if (in_pattern) {
2929 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2930 goto error;
2931 }
2932 if (!*(args[cur_arg+1])) {
2933 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2934 goto error;
2935 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002936 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002937 cur_arg++;
2938 min_recv = atol(args[cur_arg]);
2939 if (min_recv < -1 || min_recv > INT_MAX) {
2940 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2941 goto error;
2942 }
2943 }
2944 else if (*(args[cur_arg]) == '!') {
2945 in_pattern = 1;
2946 while (*(args[cur_arg]) == '!') {
2947 inverse = !inverse;
2948 args[cur_arg]++;
2949 }
2950 if (!*(args[cur_arg]))
2951 cur_arg++;
2952 goto rescan;
2953 }
2954 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2955 if (type != TCPCHK_EXPECT_UNDEF) {
2956 memprintf(errmsg, "only on pattern expected");
2957 goto error;
2958 }
2959 if (proto != TCPCHK_RULES_HTTP_CHK)
2960 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2961 else
2962 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2963
2964 if (!*(args[cur_arg+1])) {
2965 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2966 goto error;
2967 }
2968 cur_arg++;
2969 pattern = args[cur_arg];
2970 }
2971 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2972 if (proto == TCPCHK_RULES_HTTP_CHK)
2973 goto bad_http_kw;
2974 if (type != TCPCHK_EXPECT_UNDEF) {
2975 memprintf(errmsg, "only on pattern expected");
2976 goto error;
2977 }
2978 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2979
2980 if (!*(args[cur_arg+1])) {
2981 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2982 goto error;
2983 }
2984 cur_arg++;
2985 pattern = args[cur_arg];
2986 }
2987 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2988 if (type != TCPCHK_EXPECT_UNDEF) {
2989 memprintf(errmsg, "only on pattern expected");
2990 goto error;
2991 }
2992 if (proto != TCPCHK_RULES_HTTP_CHK)
2993 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2994 else {
2995 if (*(args[cur_arg]) != 's')
2996 goto bad_http_kw;
2997 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2998 }
2999
3000 if (!*(args[cur_arg+1])) {
3001 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3002 goto error;
3003 }
3004 cur_arg++;
3005 pattern = args[cur_arg];
3006 }
3007 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3008 if (proto != TCPCHK_RULES_HTTP_CHK)
3009 goto bad_tcp_kw;
3010 if (type != TCPCHK_EXPECT_UNDEF) {
3011 memprintf(errmsg, "only on pattern expected");
3012 goto error;
3013 }
3014 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3015
3016 if (!*(args[cur_arg+1])) {
3017 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3018 goto error;
3019 }
3020 cur_arg++;
3021 pattern = args[cur_arg];
3022 }
3023 else if (strcmp(args[cur_arg], "custom") == 0) {
3024 if (in_pattern) {
3025 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3026 goto error;
3027 }
3028 if (type != TCPCHK_EXPECT_UNDEF) {
3029 memprintf(errmsg, "only on pattern expected");
3030 goto error;
3031 }
3032 type = TCPCHK_EXPECT_CUSTOM;
3033 }
3034 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3035 int orig_arg = cur_arg;
3036
3037 if (proto != TCPCHK_RULES_HTTP_CHK)
3038 goto bad_tcp_kw;
3039 if (type != TCPCHK_EXPECT_UNDEF) {
3040 memprintf(errmsg, "only on pattern expected");
3041 goto error;
3042 }
3043 type = TCPCHK_EXPECT_HTTP_HEADER;
3044
3045 if (strcmp(args[cur_arg], "fhdr") == 0)
3046 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3047
3048 /* Parse the name pattern, mandatory */
3049 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3050 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3051 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3052 args[orig_arg]);
3053 goto error;
3054 }
3055
3056 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3057 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3058
3059 cur_arg += 2;
3060 if (strcmp(args[cur_arg], "-m") == 0) {
3061 if (!*(args[cur_arg+1])) {
3062 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3063 args[orig_arg], args[cur_arg]);
3064 goto error;
3065 }
3066 if (strcmp(args[cur_arg+1], "str") == 0)
3067 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3068 else if (strcmp(args[cur_arg+1], "beg") == 0)
3069 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3070 else if (strcmp(args[cur_arg+1], "end") == 0)
3071 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3072 else if (strcmp(args[cur_arg+1], "sub") == 0)
3073 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3074 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3075 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3076 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3077 args[orig_arg]);
3078 goto error;
3079 }
3080 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3081 }
3082 else {
3083 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3084 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3085 goto error;
3086 }
3087 cur_arg += 2;
3088 }
3089 else
3090 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3091 npat = args[cur_arg];
3092
3093 if (!*(args[cur_arg+1]) ||
3094 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3095 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3096 goto next;
3097 }
3098 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3099 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3100
3101 /* Parse the value pattern, optional */
3102 if (strcmp(args[cur_arg+2], "-m") == 0) {
3103 cur_arg += 2;
3104 if (!*(args[cur_arg+1])) {
3105 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3106 args[orig_arg], args[cur_arg]);
3107 goto error;
3108 }
3109 if (strcmp(args[cur_arg+1], "str") == 0)
3110 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3111 else if (strcmp(args[cur_arg+1], "beg") == 0)
3112 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3113 else if (strcmp(args[cur_arg+1], "end") == 0)
3114 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3115 else if (strcmp(args[cur_arg+1], "sub") == 0)
3116 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3117 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3118 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3119 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3120 args[orig_arg]);
3121 goto error;
3122 }
3123 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3124 }
3125 else {
3126 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3127 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3128 goto error;
3129 }
3130 }
3131 else
3132 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3133
3134 if (!*(args[cur_arg+2])) {
3135 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3136 goto error;
3137 }
3138 vpat = args[cur_arg+2];
3139 cur_arg += 2;
3140 }
3141 else if (strcmp(args[cur_arg], "comment") == 0) {
3142 if (in_pattern) {
3143 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3144 goto error;
3145 }
3146 if (!*(args[cur_arg+1])) {
3147 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3148 goto error;
3149 }
3150 cur_arg++;
3151 free(comment);
3152 comment = strdup(args[cur_arg]);
3153 if (!comment) {
3154 memprintf(errmsg, "out of memory");
3155 goto error;
3156 }
3157 }
3158 else if (strcmp(args[cur_arg], "on-success") == 0) {
3159 if (in_pattern) {
3160 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3161 goto error;
3162 }
3163 if (!*(args[cur_arg+1])) {
3164 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3165 goto error;
3166 }
3167 cur_arg++;
3168 on_success_msg = args[cur_arg];
3169 }
3170 else if (strcmp(args[cur_arg], "on-error") == 0) {
3171 if (in_pattern) {
3172 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3173 goto error;
3174 }
3175 if (!*(args[cur_arg+1])) {
3176 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3177 goto error;
3178 }
3179 cur_arg++;
3180 on_error_msg = args[cur_arg];
3181 }
3182 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3183 if (in_pattern) {
3184 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3185 goto error;
3186 }
3187 if (!*(args[cur_arg+1])) {
3188 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3189 goto error;
3190 }
3191 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3192 ok_st = HCHK_STATUS_L7OKD;
3193 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3194 ok_st = HCHK_STATUS_L7OKCD;
3195 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3196 ok_st = HCHK_STATUS_L6OK;
3197 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3198 ok_st = HCHK_STATUS_L4OK;
3199 else {
3200 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3201 args[cur_arg], args[cur_arg+1]);
3202 goto error;
3203 }
3204 cur_arg++;
3205 }
3206 else if (strcmp(args[cur_arg], "error-status") == 0) {
3207 if (in_pattern) {
3208 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3209 goto error;
3210 }
3211 if (!*(args[cur_arg+1])) {
3212 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3213 goto error;
3214 }
3215 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3216 err_st = HCHK_STATUS_L7RSP;
3217 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3218 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003219 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3220 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003221 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3222 err_st = HCHK_STATUS_L6RSP;
3223 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3224 err_st = HCHK_STATUS_L4CON;
3225 else {
3226 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3227 args[cur_arg], args[cur_arg+1]);
3228 goto error;
3229 }
3230 cur_arg++;
3231 }
3232 else if (strcmp(args[cur_arg], "status-code") == 0) {
3233 int idx = 0;
3234
3235 if (in_pattern) {
3236 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3237 goto error;
3238 }
3239 if (!*(args[cur_arg+1])) {
3240 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3241 goto error;
3242 }
3243
3244 cur_arg++;
3245 release_sample_expr(status_expr);
3246 px->conf.args.ctx = ARGC_SRV;
3247 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet8551c342021-09-30 16:22:51 +02003248 file, line, errmsg, (px->cap & PR_CAP_DEF) ? NULL: &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003249 if (!status_expr) {
3250 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3251 goto error;
3252 }
3253 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3254 memprintf(errmsg, "error detected while parsing status-code expression : "
3255 " fetch method '%s' extracts information from '%s', "
3256 "none of which is available here.\n",
3257 args[cur_arg], sample_src_names(status_expr->fetch->use));
3258 goto error;
3259 }
3260 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3261 }
3262 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3263 if (in_pattern) {
3264 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3265 goto error;
3266 }
3267 if (!*(args[cur_arg+1])) {
3268 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3269 goto error;
3270 }
3271 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3272 tout_st = HCHK_STATUS_L7TOUT;
3273 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3274 tout_st = HCHK_STATUS_L6TOUT;
3275 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3276 tout_st = HCHK_STATUS_L4TOUT;
3277 else {
3278 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3279 args[cur_arg], args[cur_arg+1]);
3280 goto error;
3281 }
3282 cur_arg++;
3283 }
3284 else {
3285 if (proto == TCPCHK_RULES_HTTP_CHK) {
3286 bad_http_kw:
3287 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3288 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3289 }
3290 else {
3291 bad_tcp_kw:
3292 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3293 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3294 }
3295 goto error;
3296 }
3297 next:
3298 cur_arg++;
3299 }
3300
3301 chk = calloc(1, sizeof(*chk));
3302 if (!chk) {
3303 memprintf(errmsg, "out of memory");
3304 goto error;
3305 }
3306 chk->action = TCPCHK_ACT_EXPECT;
3307 LIST_INIT(&chk->expect.onerror_fmt);
3308 LIST_INIT(&chk->expect.onsuccess_fmt);
3309 chk->comment = comment; comment = NULL;
3310 chk->expect.type = type;
3311 chk->expect.min_recv = min_recv;
3312 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3313 chk->expect.ok_status = ok_st;
3314 chk->expect.err_status = err_st;
3315 chk->expect.tout_status = tout_st;
3316 chk->expect.status_expr = status_expr; status_expr = NULL;
3317
3318 if (on_success_msg) {
3319 px->conf.args.ctx = ARGC_SRV;
3320 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3321 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3322 goto error;
3323 }
3324 }
3325 if (on_error_msg) {
3326 px->conf.args.ctx = ARGC_SRV;
3327 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3328 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3329 goto error;
3330 }
3331 }
3332
3333 switch (chk->expect.type) {
3334 case TCPCHK_EXPECT_HTTP_STATUS: {
3335 const char *p = pattern;
3336 unsigned int c1,c2;
3337
3338 chk->expect.codes.codes = NULL;
3339 chk->expect.codes.num = 0;
3340 while (1) {
3341 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3342 if (*p == '-') {
3343 p++;
3344 c2 = read_uint(&p, pattern + strlen(pattern));
3345 }
3346 if (c1 > c2) {
3347 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3348 goto error;
3349 }
3350
3351 chk->expect.codes.num++;
3352 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3353 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3354 if (!chk->expect.codes.codes) {
3355 memprintf(errmsg, "out of memory");
3356 goto error;
3357 }
3358 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3359 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3360
3361 if (*p == '\0')
3362 break;
3363 if (*p != ',') {
3364 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3365 goto error;
3366 }
3367 p++;
3368 }
3369 break;
3370 }
3371 case TCPCHK_EXPECT_STRING:
3372 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003373 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003374 if (!isttest(chk->expect.data)) {
3375 memprintf(errmsg, "out of memory");
3376 goto error;
3377 }
3378 break;
3379 case TCPCHK_EXPECT_BINARY: {
3380 int len = chk->expect.data.len;
3381
3382 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3383 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3384 goto error;
3385 }
3386 chk->expect.data.len = len;
3387 break;
3388 }
3389 case TCPCHK_EXPECT_STRING_REGEX:
3390 case TCPCHK_EXPECT_BINARY_REGEX:
3391 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3392 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3393 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3394 if (!chk->expect.regex)
3395 goto error;
3396 break;
3397
3398 case TCPCHK_EXPECT_STRING_LF:
3399 case TCPCHK_EXPECT_BINARY_LF:
3400 case TCPCHK_EXPECT_HTTP_BODY_LF:
3401 LIST_INIT(&chk->expect.fmt);
3402 px->conf.args.ctx = ARGC_SRV;
3403 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3404 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3405 goto error;
3406 }
3407 break;
3408
3409 case TCPCHK_EXPECT_HTTP_HEADER:
3410 if (!npat) {
3411 memprintf(errmsg, "unexpected error, undefined header name pattern");
3412 goto error;
3413 }
3414 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3415 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3416 if (!chk->expect.hdr.name_re)
3417 goto error;
3418 }
3419 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3420 px->conf.args.ctx = ARGC_SRV;
3421 LIST_INIT(&chk->expect.hdr.name_fmt);
3422 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3423 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3424 goto error;
3425 }
3426 }
3427 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003428 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003429 if (!isttest(chk->expect.hdr.name)) {
3430 memprintf(errmsg, "out of memory");
3431 goto error;
3432 }
3433 }
3434
3435 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3436 chk->expect.hdr.value = IST_NULL;
3437 break;
3438 }
3439
3440 if (!vpat) {
3441 memprintf(errmsg, "unexpected error, undefined header value pattern");
3442 goto error;
3443 }
3444 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3445 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3446 if (!chk->expect.hdr.value_re)
3447 goto error;
3448 }
3449 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3450 px->conf.args.ctx = ARGC_SRV;
3451 LIST_INIT(&chk->expect.hdr.value_fmt);
3452 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3453 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3454 goto error;
3455 }
3456 }
3457 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003458 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003459 if (!isttest(chk->expect.hdr.value)) {
3460 memprintf(errmsg, "out of memory");
3461 goto error;
3462 }
3463 }
3464
3465 break;
3466 case TCPCHK_EXPECT_CUSTOM:
3467 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3468 break;
3469 case TCPCHK_EXPECT_UNDEF:
3470 memprintf(errmsg, "pattern not found");
3471 goto error;
3472 }
3473
3474 /* All tcp-check expect points back to the first inverse expect rule in
3475 * a chain of one or more expect rule, potentially itself.
3476 */
3477 chk->expect.head = chk;
3478 list_for_each_entry_rev(prev_check, rules, list) {
3479 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3480 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3481 chk->expect.head = prev_check;
3482 continue;
3483 }
3484 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3485 break;
3486 }
3487 return chk;
3488
3489 error:
3490 free_tcpcheck(chk, 0);
3491 free(comment);
3492 release_sample_expr(status_expr);
3493 return NULL;
3494}
3495
3496/* Overwrites fields of the old http send rule with those of the new one. When
3497 * replaced, old values are freed and replaced by the new ones. New values are
3498 * not copied but transferred. At the end <new> should be empty and can be
3499 * safely released. This function never fails.
3500 */
3501void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3502{
3503 struct logformat_node *lf, *lfb;
3504 struct tcpcheck_http_hdr *hdr, *bhdr;
3505
3506
3507 if (new->send.http.meth.str.area) {
3508 free(old->send.http.meth.str.area);
3509 old->send.http.meth.meth = new->send.http.meth.meth;
3510 old->send.http.meth.str.area = new->send.http.meth.str.area;
3511 old->send.http.meth.str.data = new->send.http.meth.str.data;
3512 new->send.http.meth.str = BUF_NULL;
3513 }
3514
3515 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3516 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3517 istfree(&old->send.http.uri);
3518 else
3519 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3520 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3521 old->send.http.uri = new->send.http.uri;
3522 new->send.http.uri = IST_NULL;
3523 }
3524 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3525 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3526 istfree(&old->send.http.uri);
3527 else
3528 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3529 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3530 LIST_INIT(&old->send.http.uri_fmt);
3531 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003532 LIST_DELETE(&lf->list);
3533 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003534 }
3535 }
3536
3537 if (isttest(new->send.http.vsn)) {
3538 istfree(&old->send.http.vsn);
3539 old->send.http.vsn = new->send.http.vsn;
3540 new->send.http.vsn = IST_NULL;
3541 }
3542
Christopher Faulet94cd9a42022-07-05 15:33:53 +02003543 if (!LIST_ISEMPTY(&new->send.http.hdrs)) {
3544 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3545 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
3546 LIST_DELETE(&hdr->list);
3547 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
3548 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02003549 }
3550
3551 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3552 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3553 istfree(&old->send.http.body);
3554 else
3555 free_tcpcheck_fmt(&old->send.http.body_fmt);
3556 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3557 old->send.http.body = new->send.http.body;
3558 new->send.http.body = IST_NULL;
3559 }
3560 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3561 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3562 istfree(&old->send.http.body);
3563 else
3564 free_tcpcheck_fmt(&old->send.http.body_fmt);
3565 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3566 LIST_INIT(&old->send.http.body_fmt);
3567 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003568 LIST_DELETE(&lf->list);
3569 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003570 }
3571 }
3572}
3573
3574/* Internal function used to add an http-check rule in a list during the config
3575 * parsing step. Depending on its type, and the previously inserted rules, a
3576 * specific action may be performed or an error may be reported. This functions
3577 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3578 * message.
3579 */
3580int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3581{
3582 struct tcpcheck_rule *r;
3583
3584 /* the implicit send rule coming from an "option httpchk" line must be
3585 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003586 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003587 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003588 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003589 * sure the ruleset remains valid.
3590 */
3591
3592 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3593 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3594 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3595 * following tests are performed :
3596 *
3597 * 1- If there is no such rule or if it is not a send rule, the implicit send
3598 * rule is pushed in front of the ruleset
3599 *
3600 * 2- If it is another implicit send rule, it is replaced with the new one.
3601 *
3602 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3603 * both, overwriting the old send rule (the explicit one) with info of the
3604 * new send rule (the implicit one).
3605 */
3606 r = get_first_tcpcheck_rule(rules);
3607 if (r && r->action == TCPCHK_ACT_CONNECT)
3608 r = get_next_tcpcheck_rule(rules, r);
3609 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003610 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003611 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003612 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003613 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003614 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003615 }
3616 else {
3617 tcpcheck_overwrite_send_http_rule(r, chk);
3618 free_tcpcheck(chk, 0);
3619 }
3620 }
3621 else {
3622 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3623 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3624 * with an existing implicit send rule, if any. At the end, if there is no error,
3625 * the rule is appended to the list.
3626 */
3627
3628 r = get_last_tcpcheck_rule(rules);
3629 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3630 /* no error */;
3631 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3632 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3633 chk->index+1);
3634 return 0;
3635 }
3636 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3637 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3638 chk->index+1);
3639 return 0;
3640 }
3641 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3642 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3643 chk->index+1);
3644 return 0;
3645 }
3646
3647 if (chk->action == TCPCHK_ACT_SEND) {
3648 r = get_first_tcpcheck_rule(rules);
3649 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3650 tcpcheck_overwrite_send_http_rule(r, chk);
3651 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003652 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003653 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3654 chk = r;
3655 }
3656 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003657 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003658 }
3659 return 1;
3660}
3661
3662/* Check tcp-check health-check configuration for the proxy <px>. */
3663static int check_proxy_tcpcheck(struct proxy *px)
3664{
3665 struct tcpcheck_rule *chk, *back;
3666 char *comment = NULL, *errmsg = NULL;
3667 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003668 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003669
3670 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3671 deinit_proxy_tcpcheck(px);
3672 goto out;
3673 }
3674
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003675 ha_free(&px->check_command);
3676 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003677
3678 if (!px->tcpcheck_rules.list) {
3679 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
3680 ret |= ERR_ALERT | ERR_FATAL;
3681 goto out;
3682 }
3683
3684 /* HTTP ruleset only : */
3685 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3686 struct tcpcheck_rule *next;
3687
3688 /* move remaining implicit send rule from "option httpchk" line to the right place.
3689 * If such rule exists, it must be the first one. In this case, the rule is moved
3690 * after the first connect rule, if any. Otherwise, nothing is done.
3691 */
3692 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3693 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3694 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3695 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003696 LIST_DELETE(&chk->list);
3697 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003698 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003699 }
3700 }
3701
3702 /* add implicit expect rule if the last one is a send. It is inherited from previous
3703 * versions where the http expect rule was optional. Now it is possible to chained
3704 * send/expect rules but the last expect may still be implicit.
3705 */
3706 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3707 if (chk && chk->action == TCPCHK_ACT_SEND) {
3708 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3709 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3710 px->conf.file, px->conf.line, &errmsg);
3711 if (!next) {
3712 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
3713 "(%s).\n", px->id, errmsg);
3714 free(errmsg);
3715 ret |= ERR_ALERT | ERR_FATAL;
3716 goto out;
3717 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003718 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003719 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003720 }
3721 }
3722
3723 /* For all ruleset: */
3724
3725 /* If there is no connect rule preceding all send / expect rules, an
3726 * implicit one is inserted before all others.
3727 */
3728 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3729 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3730 chk = calloc(1, sizeof(*chk));
3731 if (!chk) {
3732 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
3733 "(out of memory).\n", px->id);
3734 ret |= ERR_ALERT | ERR_FATAL;
3735 goto out;
3736 }
3737 chk->action = TCPCHK_ACT_CONNECT;
3738 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003739 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003740 }
3741
3742 /* Remove all comment rules. To do so, when a such rule is found, the
3743 * comment is assigned to the following rule(s).
3744 */
3745 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Faulet8af4ab82022-08-24 11:38:03 +02003746 struct tcpcheck_rule *next;
3747
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003748 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3749 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003750
3751 prev_action = chk->action;
3752 switch (chk->action) {
3753 case TCPCHK_ACT_COMMENT:
3754 free(comment);
3755 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003756 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003757 free(chk);
3758 break;
3759 case TCPCHK_ACT_CONNECT:
3760 if (!chk->comment && comment)
3761 chk->comment = strdup(comment);
Christopher Faulet8af4ab82022-08-24 11:38:03 +02003762 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3763 if (next && next->action == TCPCHK_ACT_SEND)
3764 chk->connect.options |= TCPCHK_OPT_HAS_DATA;
Tim Duesterhus588b3142020-05-29 14:35:51 +02003765 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003766 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003767 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003768 break;
3769 case TCPCHK_ACT_SEND:
3770 case TCPCHK_ACT_EXPECT:
3771 if (!chk->comment && comment)
3772 chk->comment = strdup(comment);
3773 break;
3774 }
3775 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003776 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003777
3778 out:
3779 return ret;
3780}
3781
3782void deinit_proxy_tcpcheck(struct proxy *px)
3783{
3784 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3785 px->tcpcheck_rules.flags = 0;
3786 px->tcpcheck_rules.list = NULL;
3787}
3788
3789static void deinit_tcpchecks()
3790{
3791 struct tcpcheck_ruleset *rs;
3792 struct tcpcheck_rule *r, *rb;
3793 struct ebpt_node *node, *next;
3794
3795 node = ebpt_first(&shared_tcpchecks);
3796 while (node) {
3797 next = ebpt_next(node);
3798 ebpt_delete(node);
3799 free(node->key);
3800 rs = container_of(node, typeof(*rs), node);
3801 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003802 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003803 free_tcpcheck(r, 0);
3804 }
3805 free(rs);
3806 node = next;
3807 }
3808}
3809
3810int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3811{
3812 struct tcpcheck_rule *tcpcheck, *prev_check;
3813 struct tcpcheck_expect *expect;
3814
Willy Tarreau6922e552021-03-22 21:11:45 +01003815 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003816 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003817 tcpcheck->action = TCPCHK_ACT_EXPECT;
3818
3819 expect = &tcpcheck->expect;
3820 expect->type = TCPCHK_EXPECT_STRING;
3821 LIST_INIT(&expect->onerror_fmt);
3822 LIST_INIT(&expect->onsuccess_fmt);
3823 expect->ok_status = HCHK_STATUS_L7OKD;
3824 expect->err_status = HCHK_STATUS_L7RSP;
3825 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003826 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003827 if (!isttest(expect->data)) {
3828 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3829 return 0;
3830 }
3831
3832 /* All tcp-check expect points back to the first inverse expect rule
3833 * in a chain of one or more expect rule, potentially itself.
3834 */
3835 tcpcheck->expect.head = tcpcheck;
3836 list_for_each_entry_rev(prev_check, rules->list, list) {
3837 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3838 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3839 tcpcheck->expect.head = prev_check;
3840 continue;
3841 }
3842 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3843 break;
3844 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003845 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003846 return 1;
3847}
3848
3849int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3850{
3851 struct tcpcheck_rule *tcpcheck;
3852 struct tcpcheck_send *send;
3853 const char *in;
3854 char *dst;
3855 int i;
3856
Willy Tarreau6922e552021-03-22 21:11:45 +01003857 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003858 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003859 tcpcheck->action = TCPCHK_ACT_SEND;
3860
3861 send = &tcpcheck->send;
3862 send->type = TCPCHK_SEND_STRING;
3863
3864 for (i = 0; strs[i]; i++)
3865 send->data.len += strlen(strs[i]);
3866
3867 send->data.ptr = malloc(istlen(send->data) + 1);
3868 if (!isttest(send->data)) {
3869 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3870 return 0;
3871 }
3872
3873 dst = istptr(send->data);
3874 for (i = 0; strs[i]; i++)
3875 for (in = strs[i]; (*dst = *in++); dst++);
3876 *dst = 0;
3877
Willy Tarreau2b718102021-04-21 07:32:39 +02003878 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003879 return 1;
3880}
3881
3882/* Parses the "tcp-check" proxy keyword */
3883static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003884 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003885 char **errmsg)
3886{
3887 struct tcpcheck_ruleset *rs = NULL;
3888 struct tcpcheck_rule *chk = NULL;
3889 int index, cur_arg, ret = 0;
3890
3891 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3892 ret = 1;
3893
3894 /* Deduce the ruleset name from the proxy info */
3895 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3896 ((curpx == defpx) ? "defaults" : curpx->id),
3897 curpx->conf.file, curpx->conf.line);
3898
3899 rs = find_tcpcheck_ruleset(b_orig(&trash));
3900 if (rs == NULL) {
3901 rs = create_tcpcheck_ruleset(b_orig(&trash));
3902 if (rs == NULL) {
3903 memprintf(errmsg, "out of memory.\n");
3904 goto error;
3905 }
3906 }
3907
3908 index = 0;
3909 if (!LIST_ISEMPTY(&rs->rules)) {
3910 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3911 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003912 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003913 }
3914
3915 cur_arg = 1;
3916 if (strcmp(args[cur_arg], "connect") == 0)
3917 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3918 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3919 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3920 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3921 else if (strcmp(args[cur_arg], "expect") == 0)
3922 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3923 else if (strcmp(args[cur_arg], "comment") == 0)
3924 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3925 else {
3926 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3927
3928 if (!kw) {
3929 action_kw_tcp_check_build_list(&trash);
3930 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3931 "%s%s. but got '%s'",
3932 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3933 goto error;
3934 }
3935 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3936 }
3937
3938 if (!chk) {
3939 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3940 goto error;
3941 }
3942 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3943
3944 /* No error: add the tcp-check rule in the list */
3945 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003946 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003947
3948 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3949 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3950 /* Use this ruleset if the proxy already has tcp-check enabled */
3951 curpx->tcpcheck_rules.list = &rs->rules;
3952 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3953 }
3954 else {
3955 /* mark this ruleset as unused for now */
3956 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3957 }
3958
3959 return ret;
3960
3961 error:
3962 free_tcpcheck(chk, 0);
3963 free_tcpcheck_ruleset(rs);
3964 return -1;
3965}
3966
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003967/* Parses the "http-check" proxy keyword */
3968static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003969 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003970 char **errmsg)
3971{
3972 struct tcpcheck_ruleset *rs = NULL;
3973 struct tcpcheck_rule *chk = NULL;
3974 int index, cur_arg, ret = 0;
3975
3976 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3977 ret = 1;
3978
3979 cur_arg = 1;
3980 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3981 /* enable a graceful server shutdown on an HTTP 404 response */
3982 curpx->options |= PR_O_DISABLE404;
3983 if (too_many_args(1, args, errmsg, NULL))
3984 goto error;
3985 goto out;
3986 }
3987 else if (strcmp(args[cur_arg], "send-state") == 0) {
3988 /* enable emission of the apparent state of a server in HTTP checks */
3989 curpx->options2 |= PR_O2_CHK_SNDST;
3990 if (too_many_args(1, args, errmsg, NULL))
3991 goto error;
3992 goto out;
3993 }
3994
3995 /* Deduce the ruleset name from the proxy info */
3996 chunk_printf(&trash, "*http-check-%s_%s-%d",
3997 ((curpx == defpx) ? "defaults" : curpx->id),
3998 curpx->conf.file, curpx->conf.line);
3999
4000 rs = find_tcpcheck_ruleset(b_orig(&trash));
4001 if (rs == NULL) {
4002 rs = create_tcpcheck_ruleset(b_orig(&trash));
4003 if (rs == NULL) {
4004 memprintf(errmsg, "out of memory.\n");
4005 goto error;
4006 }
4007 }
4008
4009 index = 0;
4010 if (!LIST_ISEMPTY(&rs->rules)) {
4011 chk = LIST_PREV(&rs->rules, typeof(chk), list);
4012 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4013 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004014 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004015 }
4016
4017 if (strcmp(args[cur_arg], "connect") == 0)
4018 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4019 else if (strcmp(args[cur_arg], "send") == 0)
4020 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4021 else if (strcmp(args[cur_arg], "expect") == 0)
4022 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4023 file, line, errmsg);
4024 else if (strcmp(args[cur_arg], "comment") == 0)
4025 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4026 else {
4027 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4028
4029 if (!kw) {
4030 action_kw_tcp_check_build_list(&trash);
4031 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4032 " 'send', 'expect'%s%s. but got '%s'",
4033 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4034 goto error;
4035 }
4036 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4037 }
4038
4039 if (!chk) {
4040 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4041 goto error;
4042 }
4043 ret = (*errmsg != NULL); /* Handle warning */
4044
4045 chk->index = index;
4046 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4047 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4048 /* Use this ruleset if the proxy already has http-check enabled */
4049 curpx->tcpcheck_rules.list = &rs->rules;
4050 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4051 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4052 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4053 curpx->tcpcheck_rules.list = NULL;
4054 goto error;
4055 }
4056 }
4057 else {
4058 /* mark this ruleset as unused for now */
4059 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004060 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004061 }
4062
4063 out:
4064 return ret;
4065
4066 error:
4067 free_tcpcheck(chk, 0);
4068 free_tcpcheck_ruleset(rs);
4069 return -1;
4070}
4071
4072/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004073int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004074 const char *file, int line)
4075{
4076 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4077 static char *redis_res = "+PONG\r\n";
4078
4079 struct tcpcheck_ruleset *rs = NULL;
4080 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4081 struct tcpcheck_rule *chk;
4082 char *errmsg = NULL;
4083 int err_code = 0;
4084
4085 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4086 err_code |= ERR_WARN;
4087
4088 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4089 goto out;
4090
4091 curpx->options2 &= ~PR_O2_CHK_ANY;
4092 curpx->options2 |= PR_O2_TCPCHK_CHK;
4093
4094 free_tcpcheck_vars(&rules->preset_vars);
4095 rules->list = NULL;
4096 rules->flags = 0;
4097
4098 rs = find_tcpcheck_ruleset("*redis-check");
4099 if (rs)
4100 goto ruleset_found;
4101
4102 rs = create_tcpcheck_ruleset("*redis-check");
4103 if (rs == NULL) {
4104 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4105 goto error;
4106 }
4107
4108 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4109 1, curpx, &rs->rules, file, line, &errmsg);
4110 if (!chk) {
4111 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4112 goto error;
4113 }
4114 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004115 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004116
4117 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4118 "error-status", "L7STS",
4119 "on-error", "%[res.payload(0,0),cut_crlf]",
4120 "on-success", "Redis server is ok",
4121 ""},
4122 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4123 if (!chk) {
4124 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4125 goto error;
4126 }
4127 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004128 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004129
4130 ruleset_found:
4131 rules->list = &rs->rules;
4132 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4133 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4134
4135 out:
4136 free(errmsg);
4137 return err_code;
4138
4139 error:
4140 free_tcpcheck_ruleset(rs);
4141 err_code |= ERR_ALERT | ERR_FATAL;
4142 goto out;
4143}
4144
4145
4146/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004147int 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 +01004148 const char *file, int line)
4149{
4150 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4151 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4152 *
4153 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4154 */
4155 static char sslv3_client_hello[] = {
4156 "16" /* ContentType : 0x16 = Handshake */
4157 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4158 "0079" /* ContentLength : 0x79 bytes after this one */
4159 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4160 "000075" /* HandshakeLength : 0x75 bytes after this one */
4161 "0300" /* Hello Version : 0x0300 = v3 */
4162 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4163 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4164 "00" /* Session ID length : empty (no session ID) */
4165 "004E" /* Cipher Suite Length : 78 bytes after this one */
4166 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4167 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4168 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4169 "000D" "000E" "000F" "0010" /* various bit lengths, */
4170 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4171 "0015" "0016" "0017" "0018"
4172 "0019" "001A" "001B" "002F"
4173 "0030" "0031" "0032" "0033"
4174 "0034" "0035" "0036" "0037"
4175 "0038" "0039" "003A"
4176 "01" /* Compression Length : 0x01 = 1 byte for types */
4177 "00" /* Compression Type : 0x00 = NULL compression */
4178 };
4179
4180 struct tcpcheck_ruleset *rs = NULL;
4181 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4182 struct tcpcheck_rule *chk;
4183 char *errmsg = NULL;
4184 int err_code = 0;
4185
4186 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4187 err_code |= ERR_WARN;
4188
4189 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4190 goto out;
4191
4192 curpx->options2 &= ~PR_O2_CHK_ANY;
4193 curpx->options2 |= PR_O2_TCPCHK_CHK;
4194
4195 free_tcpcheck_vars(&rules->preset_vars);
4196 rules->list = NULL;
4197 rules->flags = 0;
4198
4199 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4200 if (rs)
4201 goto ruleset_found;
4202
4203 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4204 if (rs == NULL) {
4205 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4206 goto error;
4207 }
4208
4209 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4210 1, curpx, &rs->rules, file, line, &errmsg);
4211 if (!chk) {
4212 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4213 goto error;
4214 }
4215 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004216 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004217
4218 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4219 "min-recv", "5", "ok-status", "L6OK",
4220 "error-status", "L6RSP", "tout-status", "L6TOUT",
4221 ""},
4222 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4223 if (!chk) {
4224 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4225 goto error;
4226 }
4227 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004228 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004229
4230 ruleset_found:
4231 rules->list = &rs->rules;
4232 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4233 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4234
4235 out:
4236 free(errmsg);
4237 return err_code;
4238
4239 error:
4240 free_tcpcheck_ruleset(rs);
4241 err_code |= ERR_ALERT | ERR_FATAL;
4242 goto out;
4243}
4244
4245/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004246int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004247 const char *file, int line)
4248{
4249 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4250
4251 struct tcpcheck_ruleset *rs = NULL;
4252 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4253 struct tcpcheck_rule *chk;
4254 struct tcpcheck_var *var = NULL;
4255 char *cmd = NULL, *errmsg = NULL;
4256 int err_code = 0;
4257
4258 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4259 err_code |= ERR_WARN;
4260
4261 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4262 goto out;
4263
4264 curpx->options2 &= ~PR_O2_CHK_ANY;
4265 curpx->options2 |= PR_O2_TCPCHK_CHK;
4266
4267 free_tcpcheck_vars(&rules->preset_vars);
4268 rules->list = NULL;
4269 rules->flags = 0;
4270
4271 cur_arg += 2;
4272 if (*args[cur_arg] && *args[cur_arg+1] &&
4273 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4274 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4275 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4276 if (cmd)
4277 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4278 }
4279 else {
4280 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4281 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4282 cmd = strdup("HELO localhost");
4283 }
4284
4285 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4286 if (cmd == NULL || var == NULL) {
4287 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4288 goto error;
4289 }
4290 var->data.type = SMP_T_STR;
4291 var->data.u.str.area = cmd;
4292 var->data.u.str.data = strlen(cmd);
4293 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004294 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004295 cmd = NULL;
4296 var = NULL;
4297
4298 rs = find_tcpcheck_ruleset("*smtp-check");
4299 if (rs)
4300 goto ruleset_found;
4301
4302 rs = create_tcpcheck_ruleset("*smtp-check");
4303 if (rs == NULL) {
4304 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4305 goto error;
4306 }
4307
4308 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4309 1, curpx, &rs->rules, file, line, &errmsg);
4310 if (!chk) {
4311 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4312 goto error;
4313 }
4314 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004315 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004316
4317 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4318 "min-recv", "4",
4319 "error-status", "L7RSP",
4320 "on-error", "%[res.payload(0,0),cut_crlf]",
4321 ""},
4322 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4323 if (!chk) {
4324 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4325 goto error;
4326 }
4327 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004328 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004329
4330 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4331 "min-recv", "4",
4332 "error-status", "L7STS",
4333 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4334 "status-code", "res.payload(0,3)",
4335 ""},
4336 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4337 if (!chk) {
4338 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4339 goto error;
4340 }
4341 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004342 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004343
4344 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4345 1, curpx, &rs->rules, file, line, &errmsg);
4346 if (!chk) {
4347 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4348 goto error;
4349 }
4350 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004351 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004352
Christopher Faulet96878442022-09-21 14:42:47 +02004353 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^(2[0-9]{2}-[^\r]*\r\n)*2[0-9]{2}[ \r]",
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004354 "error-status", "L7STS",
4355 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4356 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4357 "status-code", "res.payload(0,3)",
4358 ""},
4359 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4360 if (!chk) {
4361 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4362 goto error;
4363 }
4364 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004365 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004366
wrightlawd67b10f2022-09-08 16:10:48 +01004367 /* Send an SMTP QUIT to ensure clean disconnect (issue 1812), and expect a 2xx response code */
4368
4369 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "QUIT\r\n", ""},
4370 1, curpx, &rs->rules, file, line, &errmsg);
4371 if (!chk) {
4372 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4373 goto error;
4374 }
4375 chk->index = 5;
4376 LIST_APPEND(&rs->rules, &chk->list);
4377
4378 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4379 "min-recv", "4",
4380 "error-status", "L7STS",
4381 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4382 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4383 "status-code", "res.payload(0,3)",
4384 ""},
4385 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4386 if (!chk) {
4387 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4388 goto error;
4389 }
4390 chk->index = 6;
4391 LIST_APPEND(&rs->rules, &chk->list);
4392
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004393 ruleset_found:
4394 rules->list = &rs->rules;
4395 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4396 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4397
4398 out:
4399 free(errmsg);
4400 return err_code;
4401
4402 error:
4403 free(cmd);
4404 free(var);
4405 free_tcpcheck_vars(&rules->preset_vars);
4406 free_tcpcheck_ruleset(rs);
4407 err_code |= ERR_ALERT | ERR_FATAL;
4408 goto out;
4409}
4410
4411/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004412int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004413 const char *file, int line)
4414{
4415 static char pgsql_req[] = {
4416 "%[var(check.plen),htonl,hex]" /* The packet length*/
4417 "00030000" /* the version 3.0 */
4418 "7573657200" /* "user" key */
4419 "%[var(check.username),hex]00" /* the username */
4420 "00"
4421 };
4422
4423 struct tcpcheck_ruleset *rs = NULL;
4424 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4425 struct tcpcheck_rule *chk;
4426 struct tcpcheck_var *var = NULL;
4427 char *user = NULL, *errmsg = NULL;
4428 size_t packetlen = 0;
4429 int err_code = 0;
4430
4431 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4432 err_code |= ERR_WARN;
4433
4434 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4435 goto out;
4436
4437 curpx->options2 &= ~PR_O2_CHK_ANY;
4438 curpx->options2 |= PR_O2_TCPCHK_CHK;
4439
4440 free_tcpcheck_vars(&rules->preset_vars);
4441 rules->list = NULL;
4442 rules->flags = 0;
4443
4444 cur_arg += 2;
4445 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4446 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4447 file, line, args[0], args[1]);
4448 goto error;
4449 }
4450 if (strcmp(args[cur_arg], "user") == 0) {
4451 packetlen = 15 + strlen(args[cur_arg+1]);
4452 user = strdup(args[cur_arg+1]);
4453
4454 var = create_tcpcheck_var(ist("check.username"));
4455 if (user == NULL || var == NULL) {
4456 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4457 goto error;
4458 }
4459 var->data.type = SMP_T_STR;
4460 var->data.u.str.area = user;
4461 var->data.u.str.data = strlen(user);
4462 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004463 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004464 user = NULL;
4465 var = NULL;
4466
4467 var = create_tcpcheck_var(ist("check.plen"));
4468 if (var == NULL) {
4469 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4470 goto error;
4471 }
4472 var->data.type = SMP_T_SINT;
4473 var->data.u.sint = packetlen;
4474 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004475 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004476 var = NULL;
4477 }
4478 else {
4479 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4480 file, line, args[0], args[1]);
4481 goto error;
4482 }
4483
4484 rs = find_tcpcheck_ruleset("*pgsql-check");
4485 if (rs)
4486 goto ruleset_found;
4487
4488 rs = create_tcpcheck_ruleset("*pgsql-check");
4489 if (rs == NULL) {
4490 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4491 goto error;
4492 }
4493
4494 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4495 1, curpx, &rs->rules, file, line, &errmsg);
4496 if (!chk) {
4497 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4498 goto error;
4499 }
4500 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004501 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004502
4503 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4504 1, curpx, &rs->rules, file, line, &errmsg);
4505 if (!chk) {
4506 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4507 goto error;
4508 }
4509 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004510 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004511
4512 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4513 "min-recv", "5",
4514 "error-status", "L7RSP",
4515 "on-error", "%[res.payload(6,0)]",
4516 ""},
4517 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4518 if (!chk) {
4519 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4520 goto error;
4521 }
4522 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004523 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004524
Fatih Acar80cff2f2022-09-26 17:27:11 +02004525 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000[A-Z0-9]{2}000000(00|02|03|04|05|06|07|09|0A)",
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004526 "min-recv", "9",
4527 "error-status", "L7STS",
4528 "on-success", "PostgreSQL server is ok",
4529 "on-error", "PostgreSQL unknown error",
4530 ""},
4531 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4532 if (!chk) {
4533 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4534 goto error;
4535 }
4536 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004537 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004538
4539 ruleset_found:
4540 rules->list = &rs->rules;
4541 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4542 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4543
4544 out:
4545 free(errmsg);
4546 return err_code;
4547
4548 error:
4549 free(user);
4550 free(var);
4551 free_tcpcheck_vars(&rules->preset_vars);
4552 free_tcpcheck_ruleset(rs);
4553 err_code |= ERR_ALERT | ERR_FATAL;
4554 goto out;
4555}
4556
4557
4558/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004559int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004560 const char *file, int line)
4561{
4562 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4563 * const char mysql40_client_auth_pkt[] = {
4564 * "\x0e\x00\x00" // packet length
4565 * "\x01" // packet number
4566 * "\x00\x00" // client capabilities
4567 * "\x00\x00\x01" // max packet
4568 * "haproxy\x00" // username (null terminated string)
4569 * "\x00" // filler (always 0x00)
4570 * "\x01\x00\x00" // packet length
4571 * "\x00" // packet number
4572 * "\x01" // COM_QUIT command
4573 * };
4574 */
4575 static char mysql40_rsname[] = "*mysql40-check";
4576 static char mysql40_req[] = {
4577 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4578 "0080" /* client capabilities */
4579 "000001" /* max packet */
4580 "%[var(check.username),hex]00" /* the username */
4581 "00" /* filler (always 0x00) */
4582 "010000" /* packet length*/
4583 "00" /* sequence ID */
4584 "01" /* COM_QUIT command */
4585 };
4586
4587 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4588 * const char mysql41_client_auth_pkt[] = {
4589 * "\x0e\x00\x00\" // packet length
4590 * "\x01" // packet number
4591 * "\x00\x00\x00\x00" // client capabilities
4592 * "\x00\x00\x00\x01" // max packet
4593 * "\x21" // character set (UTF-8)
4594 * char[23] // All zeroes
4595 * "haproxy\x00" // username (null terminated string)
4596 * "\x00" // filler (always 0x00)
4597 * "\x01\x00\x00" // packet length
4598 * "\x00" // packet number
4599 * "\x01" // COM_QUIT command
4600 * };
4601 */
4602 static char mysql41_rsname[] = "*mysql41-check";
4603 static char mysql41_req[] = {
4604 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4605 "00820000" /* client capabilities */
4606 "00800001" /* max packet */
4607 "21" /* character set (UTF-8) */
4608 "000000000000000000000000" /* 23 bytes, al zeroes */
4609 "0000000000000000000000"
4610 "%[var(check.username),hex]00" /* the username */
4611 "00" /* filler (always 0x00) */
4612 "010000" /* packet length*/
4613 "00" /* sequence ID */
4614 "01" /* COM_QUIT command */
4615 };
4616
4617 struct tcpcheck_ruleset *rs = NULL;
4618 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4619 struct tcpcheck_rule *chk;
4620 struct tcpcheck_var *var = NULL;
4621 char *mysql_rsname = "*mysql-check";
4622 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4623 int index = 0, err_code = 0;
4624
4625 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4626 err_code |= ERR_WARN;
4627
4628 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4629 goto out;
4630
4631 curpx->options2 &= ~PR_O2_CHK_ANY;
4632 curpx->options2 |= PR_O2_TCPCHK_CHK;
4633
4634 free_tcpcheck_vars(&rules->preset_vars);
4635 rules->list = NULL;
4636 rules->flags = 0;
4637
4638 cur_arg += 2;
4639 if (*args[cur_arg]) {
4640 int packetlen, userlen;
4641
4642 if (strcmp(args[cur_arg], "user") != 0) {
4643 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4644 file, line, args[0], args[1], args[cur_arg]);
4645 goto error;
4646 }
4647
4648 if (*(args[cur_arg+1]) == 0) {
4649 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4650 file, line, args[0], args[1], args[cur_arg]);
4651 goto error;
4652 }
4653
4654 hdr = calloc(4, sizeof(*hdr));
4655 user = strdup(args[cur_arg+1]);
4656 userlen = strlen(args[cur_arg+1]);
4657
4658 if (hdr == NULL || user == NULL) {
4659 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4660 goto error;
4661 }
4662
4663 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4664 packetlen = userlen + 7 + 27;
4665 mysql_req = mysql41_req;
4666 mysql_rsname = mysql41_rsname;
4667 }
4668 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4669 packetlen = userlen + 7;
4670 mysql_req = mysql40_req;
4671 mysql_rsname = mysql40_rsname;
4672 }
4673 else {
4674 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4675 file, line, args[cur_arg], args[cur_arg+2]);
4676 goto error;
4677 }
4678
4679 hdr[0] = (unsigned char)(packetlen & 0xff);
4680 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4681 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4682 hdr[3] = 1;
4683
4684 var = create_tcpcheck_var(ist("check.header"));
4685 if (var == NULL) {
4686 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4687 goto error;
4688 }
4689 var->data.type = SMP_T_STR;
4690 var->data.u.str.area = hdr;
4691 var->data.u.str.data = 4;
4692 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004693 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004694 hdr = NULL;
4695 var = NULL;
4696
4697 var = create_tcpcheck_var(ist("check.username"));
4698 if (var == NULL) {
4699 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4700 goto error;
4701 }
4702 var->data.type = SMP_T_STR;
4703 var->data.u.str.area = user;
4704 var->data.u.str.data = strlen(user);
4705 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004706 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004707 user = NULL;
4708 var = NULL;
4709 }
4710
4711 rs = find_tcpcheck_ruleset(mysql_rsname);
4712 if (rs)
4713 goto ruleset_found;
4714
4715 rs = create_tcpcheck_ruleset(mysql_rsname);
4716 if (rs == NULL) {
4717 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4718 goto error;
4719 }
4720
4721 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4722 1, curpx, &rs->rules, file, line, &errmsg);
4723 if (!chk) {
4724 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4725 goto error;
4726 }
4727 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004728 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004729
4730 if (mysql_req) {
4731 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4732 1, curpx, &rs->rules, file, line, &errmsg);
4733 if (!chk) {
4734 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4735 goto error;
4736 }
4737 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004738 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004739 }
4740
4741 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4742 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4743 if (!chk) {
4744 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4745 goto error;
4746 }
4747 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4748 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004749 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004750
4751 if (mysql_req) {
4752 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4753 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4754 if (!chk) {
4755 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4756 goto error;
4757 }
4758 chk->expect.custom = tcpcheck_mysql_expect_ok;
4759 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004760 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004761 }
4762
4763 ruleset_found:
4764 rules->list = &rs->rules;
4765 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4766 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4767
4768 out:
4769 free(errmsg);
4770 return err_code;
4771
4772 error:
4773 free(hdr);
4774 free(user);
4775 free(var);
4776 free_tcpcheck_vars(&rules->preset_vars);
4777 free_tcpcheck_ruleset(rs);
4778 err_code |= ERR_ALERT | ERR_FATAL;
4779 goto out;
4780}
4781
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004782int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004783 const char *file, int line)
4784{
4785 static char *ldap_req = "300C020101600702010304008000";
4786
4787 struct tcpcheck_ruleset *rs = NULL;
4788 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4789 struct tcpcheck_rule *chk;
4790 char *errmsg = NULL;
4791 int err_code = 0;
4792
4793 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4794 err_code |= ERR_WARN;
4795
4796 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4797 goto out;
4798
4799 curpx->options2 &= ~PR_O2_CHK_ANY;
4800 curpx->options2 |= PR_O2_TCPCHK_CHK;
4801
4802 free_tcpcheck_vars(&rules->preset_vars);
4803 rules->list = NULL;
4804 rules->flags = 0;
4805
4806 rs = find_tcpcheck_ruleset("*ldap-check");
4807 if (rs)
4808 goto ruleset_found;
4809
4810 rs = create_tcpcheck_ruleset("*ldap-check");
4811 if (rs == NULL) {
4812 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4813 goto error;
4814 }
4815
4816 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4817 1, curpx, &rs->rules, file, line, &errmsg);
4818 if (!chk) {
4819 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4820 goto error;
4821 }
4822 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004823 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004824
4825 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4826 "min-recv", "14",
4827 "on-error", "Not LDAPv3 protocol",
4828 ""},
4829 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4830 if (!chk) {
4831 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4832 goto error;
4833 }
4834 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004835 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004836
4837 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4838 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4839 if (!chk) {
4840 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4841 goto error;
4842 }
4843 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4844 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004845 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004846
4847 ruleset_found:
4848 rules->list = &rs->rules;
4849 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4850 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4851
4852 out:
4853 free(errmsg);
4854 return err_code;
4855
4856 error:
4857 free_tcpcheck_ruleset(rs);
4858 err_code |= ERR_ALERT | ERR_FATAL;
4859 goto out;
4860}
4861
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004862int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004863 const char *file, int line)
4864{
4865 struct tcpcheck_ruleset *rs = NULL;
4866 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4867 struct tcpcheck_rule *chk;
4868 char *spop_req = NULL;
4869 char *errmsg = NULL;
4870 int spop_len = 0, err_code = 0;
4871
4872 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4873 err_code |= ERR_WARN;
4874
4875 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4876 goto out;
4877
4878 curpx->options2 &= ~PR_O2_CHK_ANY;
4879 curpx->options2 |= PR_O2_TCPCHK_CHK;
4880
4881 free_tcpcheck_vars(&rules->preset_vars);
4882 rules->list = NULL;
4883 rules->flags = 0;
4884
4885
4886 rs = find_tcpcheck_ruleset("*spop-check");
4887 if (rs)
4888 goto ruleset_found;
4889
4890 rs = create_tcpcheck_ruleset("*spop-check");
4891 if (rs == NULL) {
4892 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4893 goto error;
4894 }
4895
4896 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4897 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4898 goto error;
4899 }
4900 chunk_reset(&trash);
4901 dump_binary(&trash, spop_req, spop_len);
4902 trash.area[trash.data] = '\0';
4903
4904 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4905 1, curpx, &rs->rules, file, line, &errmsg);
4906 if (!chk) {
4907 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4908 goto error;
4909 }
4910 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004911 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004912
4913 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4914 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4915 if (!chk) {
4916 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4917 goto error;
4918 }
4919 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4920 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004921 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004922
4923 ruleset_found:
4924 rules->list = &rs->rules;
4925 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4926 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4927
4928 out:
4929 free(spop_req);
4930 free(errmsg);
4931 return err_code;
4932
4933 error:
4934 free_tcpcheck_ruleset(rs);
4935 err_code |= ERR_ALERT | ERR_FATAL;
4936 goto out;
4937}
4938
4939
4940static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4941{
4942 struct tcpcheck_rule *chk = NULL;
4943 struct tcpcheck_http_hdr *hdr = NULL;
4944 char *meth = NULL, *uri = NULL, *vsn = NULL;
4945 char *hdrs, *body;
4946
4947 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4948 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4949 if (hdrs == body)
4950 hdrs = NULL;
4951 if (hdrs) {
4952 *hdrs = '\0';
4953 hdrs +=2;
4954 }
4955 if (body) {
4956 *body = '\0';
4957 body += 4;
4958 }
4959 if (hdrs || body) {
4960 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4961 " Please, consider to use 'http-check send' directive instead.");
4962 }
4963
4964 chk = calloc(1, sizeof(*chk));
4965 if (!chk) {
4966 memprintf(errmsg, "out of memory");
4967 goto error;
4968 }
4969 chk->action = TCPCHK_ACT_SEND;
4970 chk->send.type = TCPCHK_SEND_HTTP;
4971 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4972 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4973 LIST_INIT(&chk->send.http.hdrs);
4974
4975 /* Copy the method, uri and version */
4976 if (*args[cur_arg]) {
4977 if (!*args[cur_arg+1])
4978 uri = args[cur_arg];
4979 else
4980 meth = args[cur_arg];
4981 }
4982 if (*args[cur_arg+1])
4983 uri = args[cur_arg+1];
4984 if (*args[cur_arg+2])
4985 vsn = args[cur_arg+2];
4986
4987 if (meth) {
4988 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4989 chk->send.http.meth.str.area = strdup(meth);
4990 chk->send.http.meth.str.data = strlen(meth);
4991 if (!chk->send.http.meth.str.area) {
4992 memprintf(errmsg, "out of memory");
4993 goto error;
4994 }
4995 }
4996 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004997 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004998 if (!isttest(chk->send.http.uri)) {
4999 memprintf(errmsg, "out of memory");
5000 goto error;
5001 }
5002 }
5003 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005004 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005005 if (!isttest(chk->send.http.vsn)) {
5006 memprintf(errmsg, "out of memory");
5007 goto error;
5008 }
5009 }
5010
5011 /* Copy the header */
5012 if (hdrs) {
5013 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
5014 struct h1m h1m;
5015 int i, ret;
5016
5017 /* Build and parse the request */
5018 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
5019
5020 h1m.flags = H1_MF_HDRS_ONLY;
5021 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
5022 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
5023 &h1m, NULL);
5024 if (ret <= 0) {
5025 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
5026 goto error;
5027 }
5028
5029 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
5030 hdr = calloc(1, sizeof(*hdr));
5031 if (!hdr) {
5032 memprintf(errmsg, "out of memory");
5033 goto error;
5034 }
5035 LIST_INIT(&hdr->value);
5036 hdr->name = istdup(tmp_hdrs[i].n);
5037 if (!hdr->name.ptr) {
5038 memprintf(errmsg, "out of memory");
5039 goto error;
5040 }
5041
5042 ist0(tmp_hdrs[i].v);
5043 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5044 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005045 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005046 }
5047 }
5048
5049 /* Copy the body */
5050 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005051 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005052 if (!isttest(chk->send.http.body)) {
5053 memprintf(errmsg, "out of memory");
5054 goto error;
5055 }
5056 }
5057
5058 return chk;
5059
5060 error:
5061 free_tcpcheck_http_hdr(hdr);
5062 free_tcpcheck(chk, 0);
5063 return NULL;
5064}
5065
5066/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005067int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005068 const char *file, int line)
5069{
5070 struct tcpcheck_ruleset *rs = NULL;
5071 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5072 struct tcpcheck_rule *chk;
5073 char *errmsg = NULL;
5074 int err_code = 0;
5075
5076 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5077 err_code |= ERR_WARN;
5078
5079 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5080 goto out;
5081
5082 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5083 if (!chk) {
5084 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5085 goto error;
5086 }
5087 if (errmsg) {
5088 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5089 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005090 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005091 }
5092
5093 no_request:
5094 curpx->options2 &= ~PR_O2_CHK_ANY;
5095 curpx->options2 |= PR_O2_TCPCHK_CHK;
5096
5097 free_tcpcheck_vars(&rules->preset_vars);
5098 rules->list = NULL;
5099 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5100
5101 /* Deduce the ruleset name from the proxy info */
5102 chunk_printf(&trash, "*http-check-%s_%s-%d",
5103 ((curpx == defpx) ? "defaults" : curpx->id),
5104 curpx->conf.file, curpx->conf.line);
5105
5106 rs = find_tcpcheck_ruleset(b_orig(&trash));
5107 if (rs == NULL) {
5108 rs = create_tcpcheck_ruleset(b_orig(&trash));
5109 if (rs == NULL) {
5110 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5111 goto error;
5112 }
5113 }
5114
5115 rules->list = &rs->rules;
5116 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5117 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5118 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5119 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5120 rules->list = NULL;
5121 goto error;
5122 }
5123
5124 out:
5125 free(errmsg);
5126 return err_code;
5127
5128 error:
5129 free_tcpcheck_ruleset(rs);
5130 free_tcpcheck(chk, 0);
5131 err_code |= ERR_ALERT | ERR_FATAL;
5132 goto out;
5133}
5134
5135/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005136int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005137 const char *file, int line)
5138{
5139 struct tcpcheck_ruleset *rs = NULL;
5140 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5141 int err_code = 0;
5142
5143 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5144 err_code |= ERR_WARN;
5145
5146 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5147 goto out;
5148
5149 curpx->options2 &= ~PR_O2_CHK_ANY;
5150 curpx->options2 |= PR_O2_TCPCHK_CHK;
5151
5152 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5153 /* If a tcp-check rulesset is already set, do nothing */
5154 if (rules->list)
5155 goto out;
5156
5157 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5158 * get it.
5159 */
5160 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5161 goto curpx_ruleset;
5162
5163 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5164 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5165 rs = find_tcpcheck_ruleset(b_orig(&trash));
5166 if (rs)
5167 goto ruleset_found;
5168 }
5169
5170 curpx_ruleset:
5171 /* Deduce the ruleset name from the proxy info */
5172 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5173 ((curpx == defpx) ? "defaults" : curpx->id),
5174 curpx->conf.file, curpx->conf.line);
5175
5176 rs = find_tcpcheck_ruleset(b_orig(&trash));
5177 if (rs == NULL) {
5178 rs = create_tcpcheck_ruleset(b_orig(&trash));
5179 if (rs == NULL) {
5180 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5181 goto error;
5182 }
5183 }
5184
5185 ruleset_found:
5186 free_tcpcheck_vars(&rules->preset_vars);
5187 rules->list = &rs->rules;
5188 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5189 rules->flags |= TCPCHK_RULES_TCP_CHK;
5190
5191 out:
5192 return err_code;
5193
5194 error:
5195 err_code |= ERR_ALERT | ERR_FATAL;
5196 goto out;
5197}
5198
Willy Tarreau51cd5952020-06-05 12:25:38 +02005199static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005200 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005201 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5202 { 0, NULL, NULL },
5203}};
5204
5205REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5206REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5207REGISTER_POST_DEINIT(deinit_tcpchecks);
5208INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);