blob: 6bfb601383a99e6f5163911ed0e68e67009f12f6 [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);
1418 if (!isttest(send->http.body))
1419 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;
1465 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1466
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001467 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001468 !htx_add_header(htx, ist("Content-length"), clen))
1469 goto error_htx;
1470
1471
1472 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001473 (istlen(body) && !htx_add_data_atonce(htx, body)))
1474 goto error_htx;
1475
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001476 /* no more data are expected */
1477 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001478 htx_to_buf(htx, &check->bo);
1479 break;
1480 }
1481 case TCPCHK_SEND_UNDEF:
1482 /* Should never happen. */
1483 ret = TCPCHK_EVAL_STOP;
1484 goto out;
1485 };
1486
Christopher Faulet39066c22020-11-25 13:34:51 +01001487 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001488 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001489 if (conn->mux->snd_buf(cs, &check->bo,
1490 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1491 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1492 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001493 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 +02001494 goto out;
1495 }
1496 }
Christopher Faulet18280ca2021-08-11 15:46:29 +02001497 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 +02001498 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1499 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001500 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001501 goto out;
1502 }
1503
1504 out:
1505 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001506 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1507 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001508
1509 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001510 return ret;
1511
1512 error_htx:
1513 if (htx) {
1514 htx_reset(htx);
1515 htx_to_buf(htx, &check->bo);
1516 }
1517 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1518 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001519 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 +02001520 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1521 ret = TCPCHK_EVAL_STOP;
1522 goto out;
1523
1524 error_lf:
1525 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1526 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001527 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 +02001528 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1529 ret = TCPCHK_EVAL_STOP;
1530 goto out;
1531
1532}
1533
1534/* Try to receive data before evaluating a tcp-check expect rule. Returns
1535 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1536 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1537 * TCPCHK_EVAL_STOP if an error occurred.
1538 */
1539enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1540{
1541 struct conn_stream *cs = check->cs;
1542 struct connection *conn = cs_conn(cs);
1543 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1544 size_t max, read, cur_read = 0;
1545 int is_empty;
1546 int read_poll = MAX_READ_POLL_LOOPS;
1547
Christopher Faulet147b8c92021-04-10 09:00:38 +02001548 TRACE_ENTER(CHK_EV_RX_DATA, check);
1549
1550 if (check->wait_list.events & SUB_RETRY_RECV) {
1551 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001552 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001553 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001554
1555 if (cs->flags & CS_FL_EOS)
1556 goto end_recv;
1557
Christopher Faulet147b8c92021-04-10 09:00:38 +02001558 if (check->state & CHK_ST_IN_ALLOC) {
1559 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001560 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001561 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001562
1563 if (!check_get_buf(check, &check->bi)) {
1564 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001565 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;
1567 }
1568
Willy Tarreau51cd5952020-06-05 12:25:38 +02001569 /* errors on the connection and the conn-stream were already checked */
1570
1571 /* prepare to detect if the mux needs more room */
1572 cs->flags &= ~CS_FL_WANT_ROOM;
1573
1574 while ((cs->flags & CS_FL_RCV_MORE) ||
1575 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1576 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1577 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1578 cur_read += read;
1579 if (!read ||
1580 (cs->flags & CS_FL_WANT_ROOM) ||
1581 (--read_poll <= 0) ||
1582 (read < max && read >= global.tune.recv_enough))
1583 break;
1584 }
1585
1586 end_recv:
1587 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1588 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1589 /* Report network errors only if we got no other data. Otherwise
1590 * we'll let the upper layers decide whether the response is OK
1591 * or not. It is very common that an RST sent by the server is
1592 * reported as an error just after the last data chunk.
1593 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001594 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001595 goto stop;
1596 }
1597 if (!cur_read) {
Christopher Fauletbd017472021-10-20 13:53:38 +02001598 if (cs->flags & CS_FL_EOI) {
1599 /* If EOI is set, it means there is a response or an error */
1600 goto out;
1601 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001602 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1603 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001604 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001605 goto wait_more_data;
1606 }
1607 if (is_empty) {
1608 int status;
1609
1610 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1611 tcpcheck_get_step_id(check, rule));
1612 if (rule->comment)
1613 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1614
Christopher Faulet147b8c92021-04-10 09:00:38 +02001615 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001616 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1617 set_server_check_status(check, status, trash.area);
1618 goto stop;
1619 }
1620 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001621 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001622
1623 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001624 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1625 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001626
1627 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001628 return ret;
1629
1630 stop:
1631 ret = TCPCHK_EVAL_STOP;
1632 goto out;
1633
1634 wait_more_data:
1635 ret = TCPCHK_EVAL_WAIT;
1636 goto out;
1637}
1638
1639/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1640 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1641 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1642 * error occurred.
1643 */
1644enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1645{
1646 struct htx *htx = htxbuf(&check->bi);
1647 struct htx_sl *sl;
1648 struct htx_blk *blk;
1649 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1650 struct tcpcheck_expect *expect = &rule->expect;
1651 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1652 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1653 struct ist desc = IST_NULL;
1654 int i, match, inverse;
1655
Christopher Faulet147b8c92021-04-10 09:00:38 +02001656 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1657
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001658 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001659
1660 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001661 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001662 status = HCHK_STATUS_L7RSP;
1663 goto error;
1664 }
1665
1666 if (htx_is_empty(htx)) {
1667 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001668 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001669 status = HCHK_STATUS_L7RSP;
1670 goto error;
1671 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001672 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001673 goto wait_more_data;
1674 }
1675
1676 sl = http_get_stline(htx);
1677 check->code = sl->info.res.status;
1678
1679 if (check->server &&
1680 (check->server->proxy->options & PR_O_DISABLE404) &&
1681 (check->server->next_state != SRV_ST_STOPPED) &&
1682 (check->code == 404)) {
1683 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001684 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001685 goto out;
1686 }
1687
1688 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1689 /* Make GCC happy ; initialize match to a failure state. */
1690 match = inverse;
1691 status = expect->err_status;
1692
1693 switch (expect->type) {
1694 case TCPCHK_EXPECT_HTTP_STATUS:
1695 match = 0;
1696 for (i = 0; i < expect->codes.num; i++) {
1697 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1698 sl->info.res.status <= expect->codes.codes[i][1]) {
1699 match = 1;
1700 break;
1701 }
1702 }
1703
1704 /* Set status and description in case of error */
1705 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1706 if (LIST_ISEMPTY(&expect->onerror_fmt))
1707 desc = htx_sl_res_reason(sl);
1708 break;
1709 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1710 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1711
1712 /* Set status and description in case of error */
1713 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1714 if (LIST_ISEMPTY(&expect->onerror_fmt))
1715 desc = htx_sl_res_reason(sl);
1716 break;
1717
1718 case TCPCHK_EXPECT_HTTP_HEADER: {
1719 struct http_hdr_ctx ctx;
1720 struct ist npat, vpat, value;
1721 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1722
1723 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1724 nbuf = alloc_trash_chunk();
1725 if (!nbuf) {
1726 status = HCHK_STATUS_L7RSP;
1727 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001728 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001729 goto error;
1730 }
1731 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1732 if (!b_data(nbuf)) {
1733 status = HCHK_STATUS_L7RSP;
1734 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001735 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001736 goto error;
1737 }
1738 npat = ist2(b_orig(nbuf), b_data(nbuf));
1739 }
1740 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1741 npat = expect->hdr.name;
1742
1743 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1744 vbuf = alloc_trash_chunk();
1745 if (!vbuf) {
1746 status = HCHK_STATUS_L7RSP;
1747 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001748 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001749 goto error;
1750 }
1751 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1752 if (!b_data(vbuf)) {
1753 status = HCHK_STATUS_L7RSP;
1754 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001755 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001756 goto error;
1757 }
1758 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1759 }
1760 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1761 vpat = expect->hdr.value;
1762
1763 match = 0;
1764 ctx.blk = NULL;
1765 while (1) {
1766 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1767 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1768 if (!http_find_str_header(htx, npat, &ctx, full))
1769 goto end_of_match;
1770 break;
1771 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1772 if (!http_find_pfx_header(htx, npat, &ctx, full))
1773 goto end_of_match;
1774 break;
1775 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1776 if (!http_find_sfx_header(htx, npat, &ctx, full))
1777 goto end_of_match;
1778 break;
1779 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1780 if (!http_find_sub_header(htx, npat, &ctx, full))
1781 goto end_of_match;
1782 break;
1783 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1784 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1785 goto end_of_match;
1786 break;
1787 default:
1788 /* should never happen */
1789 goto end_of_match;
1790 }
1791
1792 /* A header has matched the name pattern, let's test its
1793 * value now (always defined from there). If there is no
1794 * value pattern, it is a good match.
1795 */
1796
1797 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1798 match = 1;
1799 goto end_of_match;
1800 }
1801
1802 value = ctx.value;
1803 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1804 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1805 if (isteq(value, vpat)) {
1806 match = 1;
1807 goto end_of_match;
1808 }
1809 break;
1810 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1811 if (istlen(value) < istlen(vpat))
1812 break;
1813 value = ist2(istptr(value), istlen(vpat));
1814 if (isteq(value, vpat)) {
1815 match = 1;
1816 goto end_of_match;
1817 }
1818 break;
1819 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1820 if (istlen(value) < istlen(vpat))
1821 break;
1822 value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
1823 if (isteq(value, vpat)) {
1824 match = 1;
1825 goto end_of_match;
1826 }
1827 break;
1828 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1829 if (isttest(istist(value, vpat))) {
1830 match = 1;
1831 goto end_of_match;
1832 }
1833 break;
1834 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1835 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1836 match = 1;
1837 goto end_of_match;
1838 }
1839 break;
1840 }
1841 }
1842
1843 end_of_match:
1844 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1845 if (LIST_ISEMPTY(&expect->onerror_fmt))
1846 desc = htx_sl_res_reason(sl);
1847 break;
1848 }
1849
1850 case TCPCHK_EXPECT_HTTP_BODY:
1851 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1852 case TCPCHK_EXPECT_HTTP_BODY_LF:
1853 match = 0;
1854 chunk_reset(&trash);
1855 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1856 enum htx_blk_type type = htx_get_blk_type(blk);
1857
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001858 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001859 break;
1860 if (type == HTX_BLK_DATA) {
1861 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1862 break;
1863 }
1864 }
1865
1866 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001867 if (!last_read) {
1868 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001869 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001870 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001871 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1872 if (LIST_ISEMPTY(&expect->onerror_fmt))
1873 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001874 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001875 goto error;
1876 }
1877
1878 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1879 tmp = alloc_trash_chunk();
1880 if (!tmp) {
1881 status = HCHK_STATUS_L7RSP;
1882 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001883 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001884 goto error;
1885 }
1886 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1887 if (!b_data(tmp)) {
1888 status = HCHK_STATUS_L7RSP;
1889 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001890 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001891 goto error;
1892 }
1893 }
1894
1895 if (!last_read &&
1896 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1897 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1898 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1899 ret = TCPCHK_EVAL_WAIT;
1900 goto out;
1901 }
1902
1903 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1904 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1905 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1906 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1907 else
1908 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1909
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001910 /* Wait for more data on mismatch only if no minimum is defined (-1),
1911 * otherwise the absence of match is already conclusive.
1912 */
1913 if (!match && !last_read && (expect->min_recv == -1)) {
1914 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001915 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001916 goto out;
1917 }
1918
Willy Tarreau51cd5952020-06-05 12:25:38 +02001919 /* Set status and description in case of error */
1920 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1921 if (LIST_ISEMPTY(&expect->onerror_fmt))
1922 desc = (inverse
1923 ? ist("HTTP check matched unwanted content")
1924 : ist("HTTP content check did not match"));
1925 break;
1926
1927
1928 default:
1929 /* should never happen */
1930 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1931 goto error;
1932 }
1933
Christopher Faulet147b8c92021-04-10 09:00:38 +02001934 if (!(match ^ inverse)) {
1935 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001936 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001937 }
1938
1939 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001940
1941 out:
1942 free_trash_chunk(tmp);
1943 free_trash_chunk(nbuf);
1944 free_trash_chunk(vbuf);
1945 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001946 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001947 return ret;
1948
1949 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001950 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001951 ret = TCPCHK_EVAL_STOP;
1952 msg = alloc_trash_chunk();
1953 if (msg)
1954 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1955 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1956 goto out;
1957
1958 wait_more_data:
1959 ret = TCPCHK_EVAL_WAIT;
1960 goto out;
1961}
1962
1963/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1964 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1965 * if an error occurred.
1966 */
1967enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1968{
1969 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1970 struct tcpcheck_expect *expect = &rule->expect;
1971 struct buffer *msg = NULL, *tmp = NULL;
1972 struct ist desc = IST_NULL;
1973 enum healthcheck_status status;
1974 int match, inverse;
1975
Christopher Faulet147b8c92021-04-10 09:00:38 +02001976 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1977
Willy Tarreau51cd5952020-06-05 12:25:38 +02001978 last_read |= b_full(&check->bi);
1979
1980 /* The current expect might need more data than the previous one, check again
1981 * that the minimum amount data required to match is respected.
1982 */
1983 if (!last_read) {
1984 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1985 (b_data(&check->bi) < istlen(expect->data))) {
1986 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001987 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001988 goto out;
1989 }
1990 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1991 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001992 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001993 goto out;
1994 }
1995 }
1996
1997 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1998 /* Make GCC happy ; initialize match to a failure state. */
1999 match = inverse;
2000 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
2001
2002 switch (expect->type) {
2003 case TCPCHK_EXPECT_STRING:
2004 case TCPCHK_EXPECT_BINARY:
2005 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2006 break;
2007 case TCPCHK_EXPECT_STRING_REGEX:
2008 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2009 break;
2010
2011 case TCPCHK_EXPECT_BINARY_REGEX:
2012 chunk_reset(&trash);
2013 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2014 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2015 break;
2016
2017 case TCPCHK_EXPECT_STRING_LF:
2018 case TCPCHK_EXPECT_BINARY_LF:
2019 match = 0;
2020 tmp = alloc_trash_chunk();
2021 if (!tmp) {
2022 status = HCHK_STATUS_L7RSP;
2023 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002024 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002025 goto error;
2026 }
2027 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2028 if (!b_data(tmp)) {
2029 status = HCHK_STATUS_L7RSP;
2030 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002031 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002032 goto error;
2033 }
2034 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2035 int len = tmp->data;
2036 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2037 status = HCHK_STATUS_L7RSP;
2038 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002039 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002040 goto error;
2041 }
2042 tmp->data = len;
2043 }
2044 if (b_data(&check->bi) < tmp->data) {
2045 if (!last_read) {
2046 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002047 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002048 goto out;
2049 }
2050 break;
2051 }
2052 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2053 break;
2054
2055 case TCPCHK_EXPECT_CUSTOM:
2056 if (expect->custom)
2057 ret = expect->custom(check, rule, last_read);
2058 goto out;
2059 default:
2060 /* Should never happen. */
2061 ret = TCPCHK_EVAL_STOP;
2062 goto out;
2063 }
2064
2065
2066 /* Wait for more data on mismatch only if no minimum is defined (-1),
2067 * otherwise the absence of match is already conclusive.
2068 */
2069 if (!match && !last_read && (expect->min_recv == -1)) {
2070 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002071 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002072 goto out;
2073 }
2074
2075 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002076 if (match ^ inverse) {
2077 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002078 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002079 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002080
2081 error:
2082 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002083 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002084 ret = TCPCHK_EVAL_STOP;
2085 msg = alloc_trash_chunk();
2086 if (msg)
2087 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2088 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2089 free_trash_chunk(msg);
2090
2091 out:
2092 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002093 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002094 return ret;
2095}
2096
2097/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2098 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2099 * waits.
2100 */
2101enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2102{
2103 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2104 struct act_rule *act_rule;
2105 enum act_return act_ret;
2106
2107 act_rule =rule->action_kw.rule;
2108 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2109 if (act_ret != ACT_RET_CONT) {
2110 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2111 tcpcheck_get_step_id(check, rule));
2112 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2113 ret = TCPCHK_EVAL_STOP;
2114 }
2115
2116 return ret;
2117}
2118
2119/* Executes a tcp-check ruleset. Note that this is called both from the
2120 * connection's wake() callback and from the check scheduling task. It returns
2121 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2122 * presenting the risk of an fd replacement.
2123 *
2124 * Please do NOT place any return statement in this function and only leave
2125 * via the out_end_tcpcheck label after setting retcode.
2126 */
2127int tcpcheck_main(struct check *check)
2128{
2129 struct tcpcheck_rule *rule;
2130 struct conn_stream *cs = check->cs;
2131 struct connection *conn = cs_conn(cs);
2132 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002133 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002134 enum tcpcheck_eval_ret eval_ret;
2135
2136 /* here, we know that the check is complete or that it failed */
2137 if (check->result != CHK_RES_UNKNOWN)
2138 goto out;
2139
Christopher Faulet147b8c92021-04-10 09:00:38 +02002140 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2141
Willy Tarreau51cd5952020-06-05 12:25:38 +02002142 /* Note: the conn-stream and the connection may only be undefined before
2143 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002144 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002145 */
2146
2147 /* 1- check for connection error, if any */
2148 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2149 goto out_end_tcpcheck;
2150
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002151 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002152 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002153 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002154 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002155 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2156 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002157
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002158 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002159 * tcp-check variables */
2160 else {
2161 struct tcpcheck_var *var;
2162
2163 /* First evaluation, create a session */
2164 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2165 if (!check->sess) {
2166 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002167 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002168 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2169 goto out_end_tcpcheck;
2170 }
2171 vars_init(&check->vars, SCOPE_CHECK);
2172 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2173
2174 /* Preset tcp-check variables */
2175 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2176 struct sample smp;
2177
2178 memset(&smp, 0, sizeof(smp));
2179 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2180 smp.data = var->data;
2181 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2182 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002183 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002184 }
2185
2186 /* Now evaluate the tcp-check rules */
2187
2188 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2189 check->code = 0;
2190 switch (rule->action) {
2191 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002192 /* Not the first connection, release it first */
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002193 if (cs && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002194 check->state |= CHK_ST_CLOSE_CONN;
2195 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002196 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002197
2198 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002199
2200 /* We are still waiting the connection gets closed */
2201 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002202 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002203 eval_ret = TCPCHK_EVAL_WAIT;
2204 break;
2205 }
2206
Christopher Faulet147b8c92021-04-10 09:00:38 +02002207 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002208 eval_ret = tcpcheck_eval_connect(check, rule);
2209
2210 /* Refresh conn-stream and connection */
2211 cs = check->cs;
2212 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002213 last_read = 0;
2214 must_read = ((cs && IS_HTX_CS(cs)) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002215 break;
2216 case TCPCHK_ACT_SEND:
2217 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002218 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002219 eval_ret = tcpcheck_eval_send(check, rule);
2220 must_read = 1;
2221 break;
2222 case TCPCHK_ACT_EXPECT:
2223 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002224 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002225 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002226 eval_ret = tcpcheck_eval_recv(check, rule);
2227 if (eval_ret == TCPCHK_EVAL_STOP)
2228 goto out_end_tcpcheck;
2229 else if (eval_ret == TCPCHK_EVAL_WAIT)
2230 goto out;
2231 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2232 must_read = 0;
2233 }
2234
2235 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2236 ? tcpcheck_eval_expect_http(check, rule, last_read)
2237 : tcpcheck_eval_expect(check, rule, last_read));
2238
2239 if (eval_ret == TCPCHK_EVAL_WAIT) {
2240 check->current_step = rule->expect.head;
2241 if (!(check->wait_list.events & SUB_RETRY_RECV))
2242 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2243 }
2244 break;
2245 case TCPCHK_ACT_ACTION_KW:
2246 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002247 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002248 eval_ret = tcpcheck_eval_action_kw(check, rule);
2249 break;
2250 default:
2251 /* Otherwise, just go to the next one and don't update
2252 * the current step
2253 */
2254 eval_ret = TCPCHK_EVAL_CONTINUE;
2255 break;
2256 }
2257
2258 switch (eval_ret) {
2259 case TCPCHK_EVAL_CONTINUE:
2260 break;
2261 case TCPCHK_EVAL_WAIT:
2262 goto out;
2263 case TCPCHK_EVAL_STOP:
2264 goto out_end_tcpcheck;
2265 }
2266 }
2267
2268 /* All rules was evaluated */
2269 if (check->current_step) {
2270 rule = check->current_step;
2271
Christopher Faulet147b8c92021-04-10 09:00:38 +02002272 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2273
Willy Tarreau51cd5952020-06-05 12:25:38 +02002274 if (rule->action == TCPCHK_ACT_EXPECT) {
2275 struct buffer *msg;
2276 enum healthcheck_status status;
2277
2278 if (check->server &&
2279 (check->server->proxy->options & PR_O_DISABLE404) &&
2280 (check->server->next_state != SRV_ST_STOPPED) &&
2281 (check->code == 404)) {
2282 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002283 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002284 goto out_end_tcpcheck;
2285 }
2286
2287 msg = alloc_trash_chunk();
2288 if (msg)
2289 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2290 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2291 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2292 free_trash_chunk(msg);
2293 }
2294 else if (rule->action == TCPCHK_ACT_CONNECT) {
2295 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2296 enum healthcheck_status status = HCHK_STATUS_L4OK;
2297#ifdef USE_OPENSSL
2298 if (ssl_sock_is_ssl(conn))
2299 status = HCHK_STATUS_L6OK;
2300#endif
2301 set_server_check_status(check, status, msg);
2302 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002303 else
2304 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002305 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002306 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002307 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002308 }
2309 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002310
2311 out_end_tcpcheck:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002312 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) {
2313 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002314 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002315 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002316
Christopher Fauletb381a502020-11-25 13:47:00 +01002317 /* the tcpcheck is finished, release in/out buffer now */
2318 check_release_buf(check, &check->bi);
2319 check_release_buf(check, &check->bo);
2320
Willy Tarreau51cd5952020-06-05 12:25:38 +02002321 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002322 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002323 return retcode;
2324}
2325
2326
2327/**************************************************************************/
2328/******************* Internals to parse tcp-check rules *******************/
2329/**************************************************************************/
2330struct action_kw_list tcp_check_keywords = {
2331 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2332};
2333
2334/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2335 * returned on error.
2336 */
2337struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2338 struct list *rules, struct action_kw *kw,
2339 const char *file, int line, char **errmsg)
2340{
2341 struct tcpcheck_rule *chk = NULL;
2342 struct act_rule *actrule = NULL;
2343
2344 actrule = calloc(1, sizeof(*actrule));
2345 if (!actrule) {
2346 memprintf(errmsg, "out of memory");
2347 goto error;
2348 }
2349 actrule->kw = kw;
2350 actrule->from = ACT_F_TCP_CHK;
2351
2352 cur_arg++;
2353 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2354 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2355 goto error;
2356 }
2357
2358 chk = calloc(1, sizeof(*chk));
2359 if (!chk) {
2360 memprintf(errmsg, "out of memory");
2361 goto error;
2362 }
2363 chk->action = TCPCHK_ACT_ACTION_KW;
2364 chk->action_kw.rule = actrule;
2365 return chk;
2366
2367 error:
2368 free(actrule);
2369 return NULL;
2370}
2371
2372/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2373 * returned on error.
2374 */
2375struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2376 const char *file, int line, char **errmsg)
2377{
2378 struct tcpcheck_rule *chk = NULL;
2379 struct sockaddr_storage *sk = NULL;
2380 char *comment = NULL, *sni = NULL, *alpn = NULL;
2381 struct sample_expr *port_expr = NULL;
2382 const struct mux_proto_list *mux_proto = NULL;
2383 unsigned short conn_opts = 0;
2384 long port = 0;
2385 int alpn_len = 0;
2386
2387 list_for_each_entry(chk, rules, list) {
2388 if (chk->action == TCPCHK_ACT_CONNECT)
2389 break;
2390 if (chk->action == TCPCHK_ACT_COMMENT ||
2391 chk->action == TCPCHK_ACT_ACTION_KW ||
2392 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2393 continue;
2394
2395 memprintf(errmsg, "first step MUST also be a 'connect', "
2396 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2397 "when there is a 'connect' step in the tcp-check ruleset");
2398 goto error;
2399 }
2400
2401 cur_arg++;
2402 while (*(args[cur_arg])) {
2403 if (strcmp(args[cur_arg], "default") == 0)
2404 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2405 else if (strcmp(args[cur_arg], "addr") == 0) {
2406 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002407
2408 if (!*(args[cur_arg+1])) {
2409 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2410 goto error;
2411 }
2412
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002413 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2414 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002415 if (!sk) {
2416 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2417 goto error;
2418 }
2419
Willy Tarreau51cd5952020-06-05 12:25:38 +02002420 cur_arg++;
2421 }
2422 else if (strcmp(args[cur_arg], "port") == 0) {
2423 const char *p, *end;
2424
2425 if (!*(args[cur_arg+1])) {
2426 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2427 goto error;
2428 }
2429 cur_arg++;
2430
2431 port = 0;
2432 release_sample_expr(port_expr);
2433 p = args[cur_arg]; end = p + strlen(p);
2434 port = read_uint(&p, end);
2435 if (p != end) {
2436 int idx = 0;
2437
2438 px->conf.args.ctx = ARGC_SRV;
2439 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet8551c342021-09-30 16:22:51 +02002440 file, line, errmsg, (px->cap & PR_CAP_DEF) ? NULL: &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002441
2442 if (!port_expr) {
2443 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2444 goto error;
2445 }
2446 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2447 memprintf(errmsg, "error detected while parsing port expression : "
2448 " fetch method '%s' extracts information from '%s', "
2449 "none of which is available here.\n",
2450 args[cur_arg], sample_src_names(port_expr->fetch->use));
2451 goto error;
2452 }
2453 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2454 }
2455 else if (port > 65535 || port < 1) {
2456 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2457 args[cur_arg]);
2458 goto error;
2459 }
2460 }
2461 else if (strcmp(args[cur_arg], "proto") == 0) {
2462 if (!*(args[cur_arg+1])) {
2463 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2464 goto error;
2465 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002466 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002467 if (!mux_proto) {
2468 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2469 goto error;
2470 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002471
2472 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2473 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2474 goto error;
2475 }
2476 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2477 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2478 goto error;
2479 }
2480
Willy Tarreau51cd5952020-06-05 12:25:38 +02002481 cur_arg++;
2482 }
2483 else if (strcmp(args[cur_arg], "comment") == 0) {
2484 if (!*(args[cur_arg+1])) {
2485 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2486 goto error;
2487 }
2488 cur_arg++;
2489 free(comment);
2490 comment = strdup(args[cur_arg]);
2491 if (!comment) {
2492 memprintf(errmsg, "out of memory");
2493 goto error;
2494 }
2495 }
2496 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2497 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2498 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2499 conn_opts |= TCPCHK_OPT_SOCKS4;
2500 else if (strcmp(args[cur_arg], "linger") == 0)
2501 conn_opts |= TCPCHK_OPT_LINGER;
2502#ifdef USE_OPENSSL
2503 else if (strcmp(args[cur_arg], "ssl") == 0) {
2504 px->options |= PR_O_TCPCHK_SSL;
2505 conn_opts |= TCPCHK_OPT_SSL;
2506 }
2507 else if (strcmp(args[cur_arg], "sni") == 0) {
2508 if (!*(args[cur_arg+1])) {
2509 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2510 goto error;
2511 }
2512 cur_arg++;
2513 free(sni);
2514 sni = strdup(args[cur_arg]);
2515 if (!sni) {
2516 memprintf(errmsg, "out of memory");
2517 goto error;
2518 }
2519 }
2520 else if (strcmp(args[cur_arg], "alpn") == 0) {
2521#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2522 free(alpn);
2523 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2524 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2525 goto error;
2526 }
2527 cur_arg++;
2528#else
2529 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2530 goto error;
2531#endif
2532 }
2533#endif /* USE_OPENSSL */
2534
2535 else {
2536 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2537#ifdef USE_OPENSSL
2538 ", 'ssl', 'sni', 'alpn'"
2539#endif /* USE_OPENSSL */
2540 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2541 args[cur_arg]);
2542 goto error;
2543 }
2544 cur_arg++;
2545 }
2546
2547 chk = calloc(1, sizeof(*chk));
2548 if (!chk) {
2549 memprintf(errmsg, "out of memory");
2550 goto error;
2551 }
2552 chk->action = TCPCHK_ACT_CONNECT;
2553 chk->comment = comment;
2554 chk->connect.port = port;
2555 chk->connect.options = conn_opts;
2556 chk->connect.sni = sni;
2557 chk->connect.alpn = alpn;
2558 chk->connect.alpn_len= alpn_len;
2559 chk->connect.port_expr= port_expr;
2560 chk->connect.mux_proto= mux_proto;
2561 if (sk)
2562 chk->connect.addr = *sk;
2563 return chk;
2564
2565 error:
2566 free(alpn);
2567 free(sni);
2568 free(comment);
2569 release_sample_expr(port_expr);
2570 return NULL;
2571}
2572
2573/* Parses and creates a tcp-check send rule. NULL is returned on error */
2574struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2575 const char *file, int line, char **errmsg)
2576{
2577 struct tcpcheck_rule *chk = NULL;
2578 char *comment = NULL, *data = NULL;
2579 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2580
2581 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2582 type = TCPCHK_SEND_BINARY_LF;
2583 else if (strcmp(args[cur_arg], "send-binary") == 0)
2584 type = TCPCHK_SEND_BINARY;
2585 else if (strcmp(args[cur_arg], "send-lf") == 0)
2586 type = TCPCHK_SEND_STRING_LF;
2587 else if (strcmp(args[cur_arg], "send") == 0)
2588 type = TCPCHK_SEND_STRING;
2589
2590 if (!*(args[cur_arg+1])) {
2591 memprintf(errmsg, "'%s' expects a %s as argument",
2592 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2593 goto error;
2594 }
2595
2596 data = args[cur_arg+1];
2597
2598 cur_arg += 2;
2599 while (*(args[cur_arg])) {
2600 if (strcmp(args[cur_arg], "comment") == 0) {
2601 if (!*(args[cur_arg+1])) {
2602 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2603 goto error;
2604 }
2605 cur_arg++;
2606 free(comment);
2607 comment = strdup(args[cur_arg]);
2608 if (!comment) {
2609 memprintf(errmsg, "out of memory");
2610 goto error;
2611 }
2612 }
2613 else {
2614 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2615 args[cur_arg]);
2616 goto error;
2617 }
2618 cur_arg++;
2619 }
2620
2621 chk = calloc(1, sizeof(*chk));
2622 if (!chk) {
2623 memprintf(errmsg, "out of memory");
2624 goto error;
2625 }
2626 chk->action = TCPCHK_ACT_SEND;
2627 chk->comment = comment;
2628 chk->send.type = type;
2629
2630 switch (chk->send.type) {
2631 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002632 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002633 if (!isttest(chk->send.data)) {
2634 memprintf(errmsg, "out of memory");
2635 goto error;
2636 }
2637 break;
2638 case TCPCHK_SEND_BINARY: {
2639 int len = chk->send.data.len;
2640 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2641 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2642 goto error;
2643 }
2644 chk->send.data.len = len;
2645 break;
2646 }
2647 case TCPCHK_SEND_STRING_LF:
2648 case TCPCHK_SEND_BINARY_LF:
2649 LIST_INIT(&chk->send.fmt);
2650 px->conf.args.ctx = ARGC_SRV;
2651 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2652 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2653 goto error;
2654 }
2655 break;
2656 case TCPCHK_SEND_HTTP:
2657 case TCPCHK_SEND_UNDEF:
2658 goto error;
2659 }
2660
2661 return chk;
2662
2663 error:
2664 free(chk);
2665 free(comment);
2666 return NULL;
2667}
2668
2669/* Parses and creates a http-check send rule. NULL is returned on error */
2670struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2671 const char *file, int line, char **errmsg)
2672{
2673 struct tcpcheck_rule *chk = NULL;
2674 struct tcpcheck_http_hdr *hdr = NULL;
2675 struct http_hdr hdrs[global.tune.max_http_hdr];
2676 char *meth = NULL, *uri = NULL, *vsn = NULL;
2677 char *body = NULL, *comment = NULL;
2678 unsigned int flags = 0;
2679 int i = 0, host_hdr = -1;
2680
2681 cur_arg++;
2682 while (*(args[cur_arg])) {
2683 if (strcmp(args[cur_arg], "meth") == 0) {
2684 if (!*(args[cur_arg+1])) {
2685 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2686 goto error;
2687 }
2688 cur_arg++;
2689 meth = args[cur_arg];
2690 }
2691 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2692 if (!*(args[cur_arg+1])) {
2693 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2694 goto error;
2695 }
2696 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2697 if (strcmp(args[cur_arg], "uri-lf") == 0)
2698 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2699 cur_arg++;
2700 uri = args[cur_arg];
2701 }
2702 else if (strcmp(args[cur_arg], "ver") == 0) {
2703 if (!*(args[cur_arg+1])) {
2704 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2705 goto error;
2706 }
2707 cur_arg++;
2708 vsn = args[cur_arg];
2709 }
2710 else if (strcmp(args[cur_arg], "hdr") == 0) {
2711 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2712 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2713 goto error;
2714 }
2715
2716 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2717 if (host_hdr >= 0) {
2718 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2719 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2720 goto error;
2721 }
2722 host_hdr = i;
2723 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002724 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002725 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2726 goto skip_hdr;
2727
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002728 hdrs[i].n = ist(args[cur_arg + 1]);
2729 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002730 i++;
2731 skip_hdr:
2732 cur_arg += 2;
2733 }
2734 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2735 if (!*(args[cur_arg+1])) {
2736 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2737 goto error;
2738 }
2739 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2740 if (strcmp(args[cur_arg], "body-lf") == 0)
2741 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2742 cur_arg++;
2743 body = args[cur_arg];
2744 }
2745 else if (strcmp(args[cur_arg], "comment") == 0) {
2746 if (!*(args[cur_arg+1])) {
2747 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2748 goto error;
2749 }
2750 cur_arg++;
2751 free(comment);
2752 comment = strdup(args[cur_arg]);
2753 if (!comment) {
2754 memprintf(errmsg, "out of memory");
2755 goto error;
2756 }
2757 }
2758 else {
2759 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2760 " but got '%s' as argument.", args[cur_arg]);
2761 goto error;
2762 }
2763 cur_arg++;
2764 }
2765
2766 hdrs[i].n = hdrs[i].v = IST_NULL;
2767
2768 chk = calloc(1, sizeof(*chk));
2769 if (!chk) {
2770 memprintf(errmsg, "out of memory");
2771 goto error;
2772 }
2773 chk->action = TCPCHK_ACT_SEND;
2774 chk->comment = comment; comment = NULL;
2775 chk->send.type = TCPCHK_SEND_HTTP;
2776 chk->send.http.flags = flags;
2777 LIST_INIT(&chk->send.http.hdrs);
2778
2779 if (meth) {
2780 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2781 chk->send.http.meth.str.area = strdup(meth);
2782 chk->send.http.meth.str.data = strlen(meth);
2783 if (!chk->send.http.meth.str.area) {
2784 memprintf(errmsg, "out of memory");
2785 goto error;
2786 }
2787 }
2788 if (uri) {
2789 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2790 LIST_INIT(&chk->send.http.uri_fmt);
2791 px->conf.args.ctx = ARGC_SRV;
2792 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2793 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2794 goto error;
2795 }
2796 }
2797 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002798 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002799 if (!isttest(chk->send.http.uri)) {
2800 memprintf(errmsg, "out of memory");
2801 goto error;
2802 }
2803 }
2804 }
2805 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002806 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002807 if (!isttest(chk->send.http.vsn)) {
2808 memprintf(errmsg, "out of memory");
2809 goto error;
2810 }
2811 }
2812 for (i = 0; istlen(hdrs[i].n); i++) {
2813 hdr = calloc(1, sizeof(*hdr));
2814 if (!hdr) {
2815 memprintf(errmsg, "out of memory");
2816 goto error;
2817 }
2818 LIST_INIT(&hdr->value);
2819 hdr->name = istdup(hdrs[i].n);
2820 if (!isttest(hdr->name)) {
2821 memprintf(errmsg, "out of memory");
2822 goto error;
2823 }
2824
2825 ist0(hdrs[i].v);
2826 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2827 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002828 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002829 hdr = NULL;
2830 }
2831
2832 if (body) {
2833 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2834 LIST_INIT(&chk->send.http.body_fmt);
2835 px->conf.args.ctx = ARGC_SRV;
2836 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2837 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2838 goto error;
2839 }
2840 }
2841 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002842 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002843 if (!isttest(chk->send.http.body)) {
2844 memprintf(errmsg, "out of memory");
2845 goto error;
2846 }
2847 }
2848 }
2849
2850 return chk;
2851
2852 error:
2853 free_tcpcheck_http_hdr(hdr);
2854 free_tcpcheck(chk, 0);
2855 free(comment);
2856 return NULL;
2857}
2858
2859/* Parses and creates a http-check comment rule. NULL is returned on error */
2860struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2861 const char *file, int line, char **errmsg)
2862{
2863 struct tcpcheck_rule *chk = NULL;
2864 char *comment = NULL;
2865
2866 if (!*(args[cur_arg+1])) {
2867 memprintf(errmsg, "expects a string as argument");
2868 goto error;
2869 }
2870 cur_arg++;
2871 comment = strdup(args[cur_arg]);
2872 if (!comment) {
2873 memprintf(errmsg, "out of memory");
2874 goto error;
2875 }
2876
2877 chk = calloc(1, sizeof(*chk));
2878 if (!chk) {
2879 memprintf(errmsg, "out of memory");
2880 goto error;
2881 }
2882 chk->action = TCPCHK_ACT_COMMENT;
2883 chk->comment = comment;
2884 return chk;
2885
2886 error:
2887 free(comment);
2888 return NULL;
2889}
2890
2891/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2892 * on error. <proto> is set to the right protocol flags (covered by the
2893 * TCPCHK_RULES_PROTO_CHK mask).
2894 */
2895struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2896 struct list *rules, unsigned int proto,
2897 const char *file, int line, char **errmsg)
2898{
2899 struct tcpcheck_rule *prev_check, *chk = NULL;
2900 struct sample_expr *status_expr = NULL;
2901 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2902 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2903 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2904 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2905 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2906 unsigned int flags = 0;
2907 long min_recv = -1;
2908 int inverse = 0;
2909
2910 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2911 if (!*(args[cur_arg+1])) {
2912 memprintf(errmsg, "expects at least a matching pattern as arguments");
2913 goto error;
2914 }
2915
2916 cur_arg++;
2917 while (*(args[cur_arg])) {
2918 int in_pattern = 0;
2919
2920 rescan:
2921 if (strcmp(args[cur_arg], "min-recv") == 0) {
2922 if (in_pattern) {
2923 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2924 goto error;
2925 }
2926 if (!*(args[cur_arg+1])) {
2927 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2928 goto error;
2929 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002930 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002931 cur_arg++;
2932 min_recv = atol(args[cur_arg]);
2933 if (min_recv < -1 || min_recv > INT_MAX) {
2934 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2935 goto error;
2936 }
2937 }
2938 else if (*(args[cur_arg]) == '!') {
2939 in_pattern = 1;
2940 while (*(args[cur_arg]) == '!') {
2941 inverse = !inverse;
2942 args[cur_arg]++;
2943 }
2944 if (!*(args[cur_arg]))
2945 cur_arg++;
2946 goto rescan;
2947 }
2948 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2949 if (type != TCPCHK_EXPECT_UNDEF) {
2950 memprintf(errmsg, "only on pattern expected");
2951 goto error;
2952 }
2953 if (proto != TCPCHK_RULES_HTTP_CHK)
2954 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2955 else
2956 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2957
2958 if (!*(args[cur_arg+1])) {
2959 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2960 goto error;
2961 }
2962 cur_arg++;
2963 pattern = args[cur_arg];
2964 }
2965 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2966 if (proto == TCPCHK_RULES_HTTP_CHK)
2967 goto bad_http_kw;
2968 if (type != TCPCHK_EXPECT_UNDEF) {
2969 memprintf(errmsg, "only on pattern expected");
2970 goto error;
2971 }
2972 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2973
2974 if (!*(args[cur_arg+1])) {
2975 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2976 goto error;
2977 }
2978 cur_arg++;
2979 pattern = args[cur_arg];
2980 }
2981 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2982 if (type != TCPCHK_EXPECT_UNDEF) {
2983 memprintf(errmsg, "only on pattern expected");
2984 goto error;
2985 }
2986 if (proto != TCPCHK_RULES_HTTP_CHK)
2987 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2988 else {
2989 if (*(args[cur_arg]) != 's')
2990 goto bad_http_kw;
2991 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2992 }
2993
2994 if (!*(args[cur_arg+1])) {
2995 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2996 goto error;
2997 }
2998 cur_arg++;
2999 pattern = args[cur_arg];
3000 }
3001 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3002 if (proto != TCPCHK_RULES_HTTP_CHK)
3003 goto bad_tcp_kw;
3004 if (type != TCPCHK_EXPECT_UNDEF) {
3005 memprintf(errmsg, "only on pattern expected");
3006 goto error;
3007 }
3008 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3009
3010 if (!*(args[cur_arg+1])) {
3011 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3012 goto error;
3013 }
3014 cur_arg++;
3015 pattern = args[cur_arg];
3016 }
3017 else if (strcmp(args[cur_arg], "custom") == 0) {
3018 if (in_pattern) {
3019 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3020 goto error;
3021 }
3022 if (type != TCPCHK_EXPECT_UNDEF) {
3023 memprintf(errmsg, "only on pattern expected");
3024 goto error;
3025 }
3026 type = TCPCHK_EXPECT_CUSTOM;
3027 }
3028 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3029 int orig_arg = cur_arg;
3030
3031 if (proto != TCPCHK_RULES_HTTP_CHK)
3032 goto bad_tcp_kw;
3033 if (type != TCPCHK_EXPECT_UNDEF) {
3034 memprintf(errmsg, "only on pattern expected");
3035 goto error;
3036 }
3037 type = TCPCHK_EXPECT_HTTP_HEADER;
3038
3039 if (strcmp(args[cur_arg], "fhdr") == 0)
3040 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3041
3042 /* Parse the name pattern, mandatory */
3043 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3044 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3045 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3046 args[orig_arg]);
3047 goto error;
3048 }
3049
3050 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3051 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3052
3053 cur_arg += 2;
3054 if (strcmp(args[cur_arg], "-m") == 0) {
3055 if (!*(args[cur_arg+1])) {
3056 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3057 args[orig_arg], args[cur_arg]);
3058 goto error;
3059 }
3060 if (strcmp(args[cur_arg+1], "str") == 0)
3061 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3062 else if (strcmp(args[cur_arg+1], "beg") == 0)
3063 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3064 else if (strcmp(args[cur_arg+1], "end") == 0)
3065 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3066 else if (strcmp(args[cur_arg+1], "sub") == 0)
3067 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3068 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3069 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3070 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3071 args[orig_arg]);
3072 goto error;
3073 }
3074 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3075 }
3076 else {
3077 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3078 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3079 goto error;
3080 }
3081 cur_arg += 2;
3082 }
3083 else
3084 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3085 npat = args[cur_arg];
3086
3087 if (!*(args[cur_arg+1]) ||
3088 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3089 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3090 goto next;
3091 }
3092 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3093 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3094
3095 /* Parse the value pattern, optional */
3096 if (strcmp(args[cur_arg+2], "-m") == 0) {
3097 cur_arg += 2;
3098 if (!*(args[cur_arg+1])) {
3099 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3100 args[orig_arg], args[cur_arg]);
3101 goto error;
3102 }
3103 if (strcmp(args[cur_arg+1], "str") == 0)
3104 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3105 else if (strcmp(args[cur_arg+1], "beg") == 0)
3106 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3107 else if (strcmp(args[cur_arg+1], "end") == 0)
3108 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3109 else if (strcmp(args[cur_arg+1], "sub") == 0)
3110 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3111 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3112 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3113 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3114 args[orig_arg]);
3115 goto error;
3116 }
3117 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3118 }
3119 else {
3120 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3121 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3122 goto error;
3123 }
3124 }
3125 else
3126 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3127
3128 if (!*(args[cur_arg+2])) {
3129 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3130 goto error;
3131 }
3132 vpat = args[cur_arg+2];
3133 cur_arg += 2;
3134 }
3135 else if (strcmp(args[cur_arg], "comment") == 0) {
3136 if (in_pattern) {
3137 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3138 goto error;
3139 }
3140 if (!*(args[cur_arg+1])) {
3141 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3142 goto error;
3143 }
3144 cur_arg++;
3145 free(comment);
3146 comment = strdup(args[cur_arg]);
3147 if (!comment) {
3148 memprintf(errmsg, "out of memory");
3149 goto error;
3150 }
3151 }
3152 else if (strcmp(args[cur_arg], "on-success") == 0) {
3153 if (in_pattern) {
3154 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3155 goto error;
3156 }
3157 if (!*(args[cur_arg+1])) {
3158 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3159 goto error;
3160 }
3161 cur_arg++;
3162 on_success_msg = args[cur_arg];
3163 }
3164 else if (strcmp(args[cur_arg], "on-error") == 0) {
3165 if (in_pattern) {
3166 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3167 goto error;
3168 }
3169 if (!*(args[cur_arg+1])) {
3170 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3171 goto error;
3172 }
3173 cur_arg++;
3174 on_error_msg = args[cur_arg];
3175 }
3176 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3177 if (in_pattern) {
3178 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3179 goto error;
3180 }
3181 if (!*(args[cur_arg+1])) {
3182 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3183 goto error;
3184 }
3185 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3186 ok_st = HCHK_STATUS_L7OKD;
3187 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3188 ok_st = HCHK_STATUS_L7OKCD;
3189 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3190 ok_st = HCHK_STATUS_L6OK;
3191 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3192 ok_st = HCHK_STATUS_L4OK;
3193 else {
3194 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3195 args[cur_arg], args[cur_arg+1]);
3196 goto error;
3197 }
3198 cur_arg++;
3199 }
3200 else if (strcmp(args[cur_arg], "error-status") == 0) {
3201 if (in_pattern) {
3202 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3203 goto error;
3204 }
3205 if (!*(args[cur_arg+1])) {
3206 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3207 goto error;
3208 }
3209 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3210 err_st = HCHK_STATUS_L7RSP;
3211 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3212 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003213 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3214 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003215 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3216 err_st = HCHK_STATUS_L6RSP;
3217 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3218 err_st = HCHK_STATUS_L4CON;
3219 else {
3220 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3221 args[cur_arg], args[cur_arg+1]);
3222 goto error;
3223 }
3224 cur_arg++;
3225 }
3226 else if (strcmp(args[cur_arg], "status-code") == 0) {
3227 int idx = 0;
3228
3229 if (in_pattern) {
3230 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3231 goto error;
3232 }
3233 if (!*(args[cur_arg+1])) {
3234 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3235 goto error;
3236 }
3237
3238 cur_arg++;
3239 release_sample_expr(status_expr);
3240 px->conf.args.ctx = ARGC_SRV;
3241 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet8551c342021-09-30 16:22:51 +02003242 file, line, errmsg, (px->cap & PR_CAP_DEF) ? NULL: &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003243 if (!status_expr) {
3244 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3245 goto error;
3246 }
3247 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3248 memprintf(errmsg, "error detected while parsing status-code expression : "
3249 " fetch method '%s' extracts information from '%s', "
3250 "none of which is available here.\n",
3251 args[cur_arg], sample_src_names(status_expr->fetch->use));
3252 goto error;
3253 }
3254 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3255 }
3256 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3257 if (in_pattern) {
3258 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3259 goto error;
3260 }
3261 if (!*(args[cur_arg+1])) {
3262 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3263 goto error;
3264 }
3265 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3266 tout_st = HCHK_STATUS_L7TOUT;
3267 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3268 tout_st = HCHK_STATUS_L6TOUT;
3269 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3270 tout_st = HCHK_STATUS_L4TOUT;
3271 else {
3272 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3273 args[cur_arg], args[cur_arg+1]);
3274 goto error;
3275 }
3276 cur_arg++;
3277 }
3278 else {
3279 if (proto == TCPCHK_RULES_HTTP_CHK) {
3280 bad_http_kw:
3281 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3282 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3283 }
3284 else {
3285 bad_tcp_kw:
3286 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3287 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3288 }
3289 goto error;
3290 }
3291 next:
3292 cur_arg++;
3293 }
3294
3295 chk = calloc(1, sizeof(*chk));
3296 if (!chk) {
3297 memprintf(errmsg, "out of memory");
3298 goto error;
3299 }
3300 chk->action = TCPCHK_ACT_EXPECT;
3301 LIST_INIT(&chk->expect.onerror_fmt);
3302 LIST_INIT(&chk->expect.onsuccess_fmt);
3303 chk->comment = comment; comment = NULL;
3304 chk->expect.type = type;
3305 chk->expect.min_recv = min_recv;
3306 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3307 chk->expect.ok_status = ok_st;
3308 chk->expect.err_status = err_st;
3309 chk->expect.tout_status = tout_st;
3310 chk->expect.status_expr = status_expr; status_expr = NULL;
3311
3312 if (on_success_msg) {
3313 px->conf.args.ctx = ARGC_SRV;
3314 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3315 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3316 goto error;
3317 }
3318 }
3319 if (on_error_msg) {
3320 px->conf.args.ctx = ARGC_SRV;
3321 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3322 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3323 goto error;
3324 }
3325 }
3326
3327 switch (chk->expect.type) {
3328 case TCPCHK_EXPECT_HTTP_STATUS: {
3329 const char *p = pattern;
3330 unsigned int c1,c2;
3331
3332 chk->expect.codes.codes = NULL;
3333 chk->expect.codes.num = 0;
3334 while (1) {
3335 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3336 if (*p == '-') {
3337 p++;
3338 c2 = read_uint(&p, pattern + strlen(pattern));
3339 }
3340 if (c1 > c2) {
3341 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3342 goto error;
3343 }
3344
3345 chk->expect.codes.num++;
3346 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3347 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3348 if (!chk->expect.codes.codes) {
3349 memprintf(errmsg, "out of memory");
3350 goto error;
3351 }
3352 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3353 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3354
3355 if (*p == '\0')
3356 break;
3357 if (*p != ',') {
3358 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3359 goto error;
3360 }
3361 p++;
3362 }
3363 break;
3364 }
3365 case TCPCHK_EXPECT_STRING:
3366 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003367 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003368 if (!isttest(chk->expect.data)) {
3369 memprintf(errmsg, "out of memory");
3370 goto error;
3371 }
3372 break;
3373 case TCPCHK_EXPECT_BINARY: {
3374 int len = chk->expect.data.len;
3375
3376 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3377 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3378 goto error;
3379 }
3380 chk->expect.data.len = len;
3381 break;
3382 }
3383 case TCPCHK_EXPECT_STRING_REGEX:
3384 case TCPCHK_EXPECT_BINARY_REGEX:
3385 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3386 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3387 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3388 if (!chk->expect.regex)
3389 goto error;
3390 break;
3391
3392 case TCPCHK_EXPECT_STRING_LF:
3393 case TCPCHK_EXPECT_BINARY_LF:
3394 case TCPCHK_EXPECT_HTTP_BODY_LF:
3395 LIST_INIT(&chk->expect.fmt);
3396 px->conf.args.ctx = ARGC_SRV;
3397 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3398 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3399 goto error;
3400 }
3401 break;
3402
3403 case TCPCHK_EXPECT_HTTP_HEADER:
3404 if (!npat) {
3405 memprintf(errmsg, "unexpected error, undefined header name pattern");
3406 goto error;
3407 }
3408 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3409 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3410 if (!chk->expect.hdr.name_re)
3411 goto error;
3412 }
3413 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3414 px->conf.args.ctx = ARGC_SRV;
3415 LIST_INIT(&chk->expect.hdr.name_fmt);
3416 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3417 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3418 goto error;
3419 }
3420 }
3421 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003422 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003423 if (!isttest(chk->expect.hdr.name)) {
3424 memprintf(errmsg, "out of memory");
3425 goto error;
3426 }
3427 }
3428
3429 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3430 chk->expect.hdr.value = IST_NULL;
3431 break;
3432 }
3433
3434 if (!vpat) {
3435 memprintf(errmsg, "unexpected error, undefined header value pattern");
3436 goto error;
3437 }
3438 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3439 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3440 if (!chk->expect.hdr.value_re)
3441 goto error;
3442 }
3443 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3444 px->conf.args.ctx = ARGC_SRV;
3445 LIST_INIT(&chk->expect.hdr.value_fmt);
3446 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3447 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3448 goto error;
3449 }
3450 }
3451 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003452 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003453 if (!isttest(chk->expect.hdr.value)) {
3454 memprintf(errmsg, "out of memory");
3455 goto error;
3456 }
3457 }
3458
3459 break;
3460 case TCPCHK_EXPECT_CUSTOM:
3461 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3462 break;
3463 case TCPCHK_EXPECT_UNDEF:
3464 memprintf(errmsg, "pattern not found");
3465 goto error;
3466 }
3467
3468 /* All tcp-check expect points back to the first inverse expect rule in
3469 * a chain of one or more expect rule, potentially itself.
3470 */
3471 chk->expect.head = chk;
3472 list_for_each_entry_rev(prev_check, rules, list) {
3473 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3474 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3475 chk->expect.head = prev_check;
3476 continue;
3477 }
3478 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3479 break;
3480 }
3481 return chk;
3482
3483 error:
3484 free_tcpcheck(chk, 0);
3485 free(comment);
3486 release_sample_expr(status_expr);
3487 return NULL;
3488}
3489
3490/* Overwrites fields of the old http send rule with those of the new one. When
3491 * replaced, old values are freed and replaced by the new ones. New values are
3492 * not copied but transferred. At the end <new> should be empty and can be
3493 * safely released. This function never fails.
3494 */
3495void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3496{
3497 struct logformat_node *lf, *lfb;
3498 struct tcpcheck_http_hdr *hdr, *bhdr;
3499
3500
3501 if (new->send.http.meth.str.area) {
3502 free(old->send.http.meth.str.area);
3503 old->send.http.meth.meth = new->send.http.meth.meth;
3504 old->send.http.meth.str.area = new->send.http.meth.str.area;
3505 old->send.http.meth.str.data = new->send.http.meth.str.data;
3506 new->send.http.meth.str = BUF_NULL;
3507 }
3508
3509 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3510 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3511 istfree(&old->send.http.uri);
3512 else
3513 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3514 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3515 old->send.http.uri = new->send.http.uri;
3516 new->send.http.uri = IST_NULL;
3517 }
3518 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3519 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3520 istfree(&old->send.http.uri);
3521 else
3522 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3523 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3524 LIST_INIT(&old->send.http.uri_fmt);
3525 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003526 LIST_DELETE(&lf->list);
3527 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003528 }
3529 }
3530
3531 if (isttest(new->send.http.vsn)) {
3532 istfree(&old->send.http.vsn);
3533 old->send.http.vsn = new->send.http.vsn;
3534 new->send.http.vsn = IST_NULL;
3535 }
3536
Christopher Faulet94cd9a42022-07-05 15:33:53 +02003537 if (!LIST_ISEMPTY(&new->send.http.hdrs)) {
3538 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3539 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
3540 LIST_DELETE(&hdr->list);
3541 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
3542 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02003543 }
3544
3545 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3546 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3547 istfree(&old->send.http.body);
3548 else
3549 free_tcpcheck_fmt(&old->send.http.body_fmt);
3550 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3551 old->send.http.body = new->send.http.body;
3552 new->send.http.body = IST_NULL;
3553 }
3554 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3555 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3556 istfree(&old->send.http.body);
3557 else
3558 free_tcpcheck_fmt(&old->send.http.body_fmt);
3559 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3560 LIST_INIT(&old->send.http.body_fmt);
3561 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003562 LIST_DELETE(&lf->list);
3563 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003564 }
3565 }
3566}
3567
3568/* Internal function used to add an http-check rule in a list during the config
3569 * parsing step. Depending on its type, and the previously inserted rules, a
3570 * specific action may be performed or an error may be reported. This functions
3571 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3572 * message.
3573 */
3574int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3575{
3576 struct tcpcheck_rule *r;
3577
3578 /* the implicit send rule coming from an "option httpchk" line must be
3579 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003580 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003581 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003582 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003583 * sure the ruleset remains valid.
3584 */
3585
3586 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3587 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3588 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3589 * following tests are performed :
3590 *
3591 * 1- If there is no such rule or if it is not a send rule, the implicit send
3592 * rule is pushed in front of the ruleset
3593 *
3594 * 2- If it is another implicit send rule, it is replaced with the new one.
3595 *
3596 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3597 * both, overwriting the old send rule (the explicit one) with info of the
3598 * new send rule (the implicit one).
3599 */
3600 r = get_first_tcpcheck_rule(rules);
3601 if (r && r->action == TCPCHK_ACT_CONNECT)
3602 r = get_next_tcpcheck_rule(rules, r);
3603 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003604 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003605 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003606 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003607 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003608 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003609 }
3610 else {
3611 tcpcheck_overwrite_send_http_rule(r, chk);
3612 free_tcpcheck(chk, 0);
3613 }
3614 }
3615 else {
3616 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3617 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3618 * with an existing implicit send rule, if any. At the end, if there is no error,
3619 * the rule is appended to the list.
3620 */
3621
3622 r = get_last_tcpcheck_rule(rules);
3623 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3624 /* no error */;
3625 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3626 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3627 chk->index+1);
3628 return 0;
3629 }
3630 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3631 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3632 chk->index+1);
3633 return 0;
3634 }
3635 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3636 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3637 chk->index+1);
3638 return 0;
3639 }
3640
3641 if (chk->action == TCPCHK_ACT_SEND) {
3642 r = get_first_tcpcheck_rule(rules);
3643 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3644 tcpcheck_overwrite_send_http_rule(r, chk);
3645 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003646 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003647 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3648 chk = r;
3649 }
3650 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003651 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003652 }
3653 return 1;
3654}
3655
3656/* Check tcp-check health-check configuration for the proxy <px>. */
3657static int check_proxy_tcpcheck(struct proxy *px)
3658{
3659 struct tcpcheck_rule *chk, *back;
3660 char *comment = NULL, *errmsg = NULL;
3661 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003662 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003663
3664 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3665 deinit_proxy_tcpcheck(px);
3666 goto out;
3667 }
3668
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003669 ha_free(&px->check_command);
3670 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003671
3672 if (!px->tcpcheck_rules.list) {
3673 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
3674 ret |= ERR_ALERT | ERR_FATAL;
3675 goto out;
3676 }
3677
3678 /* HTTP ruleset only : */
3679 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3680 struct tcpcheck_rule *next;
3681
3682 /* move remaining implicit send rule from "option httpchk" line to the right place.
3683 * If such rule exists, it must be the first one. In this case, the rule is moved
3684 * after the first connect rule, if any. Otherwise, nothing is done.
3685 */
3686 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3687 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3688 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3689 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003690 LIST_DELETE(&chk->list);
3691 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003692 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003693 }
3694 }
3695
3696 /* add implicit expect rule if the last one is a send. It is inherited from previous
3697 * versions where the http expect rule was optional. Now it is possible to chained
3698 * send/expect rules but the last expect may still be implicit.
3699 */
3700 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3701 if (chk && chk->action == TCPCHK_ACT_SEND) {
3702 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3703 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3704 px->conf.file, px->conf.line, &errmsg);
3705 if (!next) {
3706 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
3707 "(%s).\n", px->id, errmsg);
3708 free(errmsg);
3709 ret |= ERR_ALERT | ERR_FATAL;
3710 goto out;
3711 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003712 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletb3cb3222021-06-25 11:37:45 +02003713 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003714 }
3715 }
3716
3717 /* For all ruleset: */
3718
3719 /* If there is no connect rule preceding all send / expect rules, an
3720 * implicit one is inserted before all others.
3721 */
3722 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3723 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3724 chk = calloc(1, sizeof(*chk));
3725 if (!chk) {
3726 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
3727 "(out of memory).\n", px->id);
3728 ret |= ERR_ALERT | ERR_FATAL;
3729 goto out;
3730 }
3731 chk->action = TCPCHK_ACT_CONNECT;
3732 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003733 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003734 }
3735
3736 /* Remove all comment rules. To do so, when a such rule is found, the
3737 * comment is assigned to the following rule(s).
3738 */
3739 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Faulet8af4ab82022-08-24 11:38:03 +02003740 struct tcpcheck_rule *next;
3741
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003742 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3743 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003744
3745 prev_action = chk->action;
3746 switch (chk->action) {
3747 case TCPCHK_ACT_COMMENT:
3748 free(comment);
3749 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003750 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003751 free(chk);
3752 break;
3753 case TCPCHK_ACT_CONNECT:
3754 if (!chk->comment && comment)
3755 chk->comment = strdup(comment);
Christopher Faulet8af4ab82022-08-24 11:38:03 +02003756 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3757 if (next && next->action == TCPCHK_ACT_SEND)
3758 chk->connect.options |= TCPCHK_OPT_HAS_DATA;
Tim Duesterhus588b3142020-05-29 14:35:51 +02003759 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003760 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003761 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003762 break;
3763 case TCPCHK_ACT_SEND:
3764 case TCPCHK_ACT_EXPECT:
3765 if (!chk->comment && comment)
3766 chk->comment = strdup(comment);
3767 break;
3768 }
3769 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003770 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003771
3772 out:
3773 return ret;
3774}
3775
3776void deinit_proxy_tcpcheck(struct proxy *px)
3777{
3778 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3779 px->tcpcheck_rules.flags = 0;
3780 px->tcpcheck_rules.list = NULL;
3781}
3782
3783static void deinit_tcpchecks()
3784{
3785 struct tcpcheck_ruleset *rs;
3786 struct tcpcheck_rule *r, *rb;
3787 struct ebpt_node *node, *next;
3788
3789 node = ebpt_first(&shared_tcpchecks);
3790 while (node) {
3791 next = ebpt_next(node);
3792 ebpt_delete(node);
3793 free(node->key);
3794 rs = container_of(node, typeof(*rs), node);
3795 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003796 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003797 free_tcpcheck(r, 0);
3798 }
3799 free(rs);
3800 node = next;
3801 }
3802}
3803
3804int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3805{
3806 struct tcpcheck_rule *tcpcheck, *prev_check;
3807 struct tcpcheck_expect *expect;
3808
Willy Tarreau6922e552021-03-22 21:11:45 +01003809 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003810 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003811 tcpcheck->action = TCPCHK_ACT_EXPECT;
3812
3813 expect = &tcpcheck->expect;
3814 expect->type = TCPCHK_EXPECT_STRING;
3815 LIST_INIT(&expect->onerror_fmt);
3816 LIST_INIT(&expect->onsuccess_fmt);
3817 expect->ok_status = HCHK_STATUS_L7OKD;
3818 expect->err_status = HCHK_STATUS_L7RSP;
3819 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003820 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003821 if (!isttest(expect->data)) {
3822 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3823 return 0;
3824 }
3825
3826 /* All tcp-check expect points back to the first inverse expect rule
3827 * in a chain of one or more expect rule, potentially itself.
3828 */
3829 tcpcheck->expect.head = tcpcheck;
3830 list_for_each_entry_rev(prev_check, rules->list, list) {
3831 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3832 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3833 tcpcheck->expect.head = prev_check;
3834 continue;
3835 }
3836 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3837 break;
3838 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003839 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003840 return 1;
3841}
3842
3843int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3844{
3845 struct tcpcheck_rule *tcpcheck;
3846 struct tcpcheck_send *send;
3847 const char *in;
3848 char *dst;
3849 int i;
3850
Willy Tarreau6922e552021-03-22 21:11:45 +01003851 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003852 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003853 tcpcheck->action = TCPCHK_ACT_SEND;
3854
3855 send = &tcpcheck->send;
3856 send->type = TCPCHK_SEND_STRING;
3857
3858 for (i = 0; strs[i]; i++)
3859 send->data.len += strlen(strs[i]);
3860
3861 send->data.ptr = malloc(istlen(send->data) + 1);
3862 if (!isttest(send->data)) {
3863 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3864 return 0;
3865 }
3866
3867 dst = istptr(send->data);
3868 for (i = 0; strs[i]; i++)
3869 for (in = strs[i]; (*dst = *in++); dst++);
3870 *dst = 0;
3871
Willy Tarreau2b718102021-04-21 07:32:39 +02003872 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003873 return 1;
3874}
3875
3876/* Parses the "tcp-check" proxy keyword */
3877static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003878 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003879 char **errmsg)
3880{
3881 struct tcpcheck_ruleset *rs = NULL;
3882 struct tcpcheck_rule *chk = NULL;
3883 int index, cur_arg, ret = 0;
3884
3885 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3886 ret = 1;
3887
3888 /* Deduce the ruleset name from the proxy info */
3889 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3890 ((curpx == defpx) ? "defaults" : curpx->id),
3891 curpx->conf.file, curpx->conf.line);
3892
3893 rs = find_tcpcheck_ruleset(b_orig(&trash));
3894 if (rs == NULL) {
3895 rs = create_tcpcheck_ruleset(b_orig(&trash));
3896 if (rs == NULL) {
3897 memprintf(errmsg, "out of memory.\n");
3898 goto error;
3899 }
3900 }
3901
3902 index = 0;
3903 if (!LIST_ISEMPTY(&rs->rules)) {
3904 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3905 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003906 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003907 }
3908
3909 cur_arg = 1;
3910 if (strcmp(args[cur_arg], "connect") == 0)
3911 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3912 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3913 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3914 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3915 else if (strcmp(args[cur_arg], "expect") == 0)
3916 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3917 else if (strcmp(args[cur_arg], "comment") == 0)
3918 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3919 else {
3920 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3921
3922 if (!kw) {
3923 action_kw_tcp_check_build_list(&trash);
3924 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3925 "%s%s. but got '%s'",
3926 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3927 goto error;
3928 }
3929 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3930 }
3931
3932 if (!chk) {
3933 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3934 goto error;
3935 }
3936 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3937
3938 /* No error: add the tcp-check rule in the list */
3939 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003940 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003941
3942 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3943 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3944 /* Use this ruleset if the proxy already has tcp-check enabled */
3945 curpx->tcpcheck_rules.list = &rs->rules;
3946 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3947 }
3948 else {
3949 /* mark this ruleset as unused for now */
3950 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3951 }
3952
3953 return ret;
3954
3955 error:
3956 free_tcpcheck(chk, 0);
3957 free_tcpcheck_ruleset(rs);
3958 return -1;
3959}
3960
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003961/* Parses the "http-check" proxy keyword */
3962static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003963 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003964 char **errmsg)
3965{
3966 struct tcpcheck_ruleset *rs = NULL;
3967 struct tcpcheck_rule *chk = NULL;
3968 int index, cur_arg, ret = 0;
3969
3970 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3971 ret = 1;
3972
3973 cur_arg = 1;
3974 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3975 /* enable a graceful server shutdown on an HTTP 404 response */
3976 curpx->options |= PR_O_DISABLE404;
3977 if (too_many_args(1, args, errmsg, NULL))
3978 goto error;
3979 goto out;
3980 }
3981 else if (strcmp(args[cur_arg], "send-state") == 0) {
3982 /* enable emission of the apparent state of a server in HTTP checks */
3983 curpx->options2 |= PR_O2_CHK_SNDST;
3984 if (too_many_args(1, args, errmsg, NULL))
3985 goto error;
3986 goto out;
3987 }
3988
3989 /* Deduce the ruleset name from the proxy info */
3990 chunk_printf(&trash, "*http-check-%s_%s-%d",
3991 ((curpx == defpx) ? "defaults" : curpx->id),
3992 curpx->conf.file, curpx->conf.line);
3993
3994 rs = find_tcpcheck_ruleset(b_orig(&trash));
3995 if (rs == NULL) {
3996 rs = create_tcpcheck_ruleset(b_orig(&trash));
3997 if (rs == NULL) {
3998 memprintf(errmsg, "out of memory.\n");
3999 goto error;
4000 }
4001 }
4002
4003 index = 0;
4004 if (!LIST_ISEMPTY(&rs->rules)) {
4005 chk = LIST_PREV(&rs->rules, typeof(chk), list);
4006 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4007 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004008 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004009 }
4010
4011 if (strcmp(args[cur_arg], "connect") == 0)
4012 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4013 else if (strcmp(args[cur_arg], "send") == 0)
4014 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4015 else if (strcmp(args[cur_arg], "expect") == 0)
4016 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4017 file, line, errmsg);
4018 else if (strcmp(args[cur_arg], "comment") == 0)
4019 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4020 else {
4021 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4022
4023 if (!kw) {
4024 action_kw_tcp_check_build_list(&trash);
4025 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4026 " 'send', 'expect'%s%s. but got '%s'",
4027 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4028 goto error;
4029 }
4030 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4031 }
4032
4033 if (!chk) {
4034 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4035 goto error;
4036 }
4037 ret = (*errmsg != NULL); /* Handle warning */
4038
4039 chk->index = index;
4040 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4041 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4042 /* Use this ruleset if the proxy already has http-check enabled */
4043 curpx->tcpcheck_rules.list = &rs->rules;
4044 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4045 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4046 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4047 curpx->tcpcheck_rules.list = NULL;
4048 goto error;
4049 }
4050 }
4051 else {
4052 /* mark this ruleset as unused for now */
4053 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004054 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004055 }
4056
4057 out:
4058 return ret;
4059
4060 error:
4061 free_tcpcheck(chk, 0);
4062 free_tcpcheck_ruleset(rs);
4063 return -1;
4064}
4065
4066/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004067int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004068 const char *file, int line)
4069{
4070 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4071 static char *redis_res = "+PONG\r\n";
4072
4073 struct tcpcheck_ruleset *rs = NULL;
4074 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4075 struct tcpcheck_rule *chk;
4076 char *errmsg = NULL;
4077 int err_code = 0;
4078
4079 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4080 err_code |= ERR_WARN;
4081
4082 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4083 goto out;
4084
4085 curpx->options2 &= ~PR_O2_CHK_ANY;
4086 curpx->options2 |= PR_O2_TCPCHK_CHK;
4087
4088 free_tcpcheck_vars(&rules->preset_vars);
4089 rules->list = NULL;
4090 rules->flags = 0;
4091
4092 rs = find_tcpcheck_ruleset("*redis-check");
4093 if (rs)
4094 goto ruleset_found;
4095
4096 rs = create_tcpcheck_ruleset("*redis-check");
4097 if (rs == NULL) {
4098 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4099 goto error;
4100 }
4101
4102 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4103 1, curpx, &rs->rules, file, line, &errmsg);
4104 if (!chk) {
4105 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4106 goto error;
4107 }
4108 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004109 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004110
4111 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4112 "error-status", "L7STS",
4113 "on-error", "%[res.payload(0,0),cut_crlf]",
4114 "on-success", "Redis server is ok",
4115 ""},
4116 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4117 if (!chk) {
4118 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4119 goto error;
4120 }
4121 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004122 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004123
4124 ruleset_found:
4125 rules->list = &rs->rules;
4126 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4127 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4128
4129 out:
4130 free(errmsg);
4131 return err_code;
4132
4133 error:
4134 free_tcpcheck_ruleset(rs);
4135 err_code |= ERR_ALERT | ERR_FATAL;
4136 goto out;
4137}
4138
4139
4140/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004141int 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 +01004142 const char *file, int line)
4143{
4144 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4145 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4146 *
4147 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4148 */
4149 static char sslv3_client_hello[] = {
4150 "16" /* ContentType : 0x16 = Handshake */
4151 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4152 "0079" /* ContentLength : 0x79 bytes after this one */
4153 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4154 "000075" /* HandshakeLength : 0x75 bytes after this one */
4155 "0300" /* Hello Version : 0x0300 = v3 */
4156 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4157 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4158 "00" /* Session ID length : empty (no session ID) */
4159 "004E" /* Cipher Suite Length : 78 bytes after this one */
4160 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4161 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4162 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4163 "000D" "000E" "000F" "0010" /* various bit lengths, */
4164 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4165 "0015" "0016" "0017" "0018"
4166 "0019" "001A" "001B" "002F"
4167 "0030" "0031" "0032" "0033"
4168 "0034" "0035" "0036" "0037"
4169 "0038" "0039" "003A"
4170 "01" /* Compression Length : 0x01 = 1 byte for types */
4171 "00" /* Compression Type : 0x00 = NULL compression */
4172 };
4173
4174 struct tcpcheck_ruleset *rs = NULL;
4175 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4176 struct tcpcheck_rule *chk;
4177 char *errmsg = NULL;
4178 int err_code = 0;
4179
4180 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4181 err_code |= ERR_WARN;
4182
4183 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4184 goto out;
4185
4186 curpx->options2 &= ~PR_O2_CHK_ANY;
4187 curpx->options2 |= PR_O2_TCPCHK_CHK;
4188
4189 free_tcpcheck_vars(&rules->preset_vars);
4190 rules->list = NULL;
4191 rules->flags = 0;
4192
4193 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4194 if (rs)
4195 goto ruleset_found;
4196
4197 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4198 if (rs == NULL) {
4199 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4200 goto error;
4201 }
4202
4203 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4204 1, curpx, &rs->rules, file, line, &errmsg);
4205 if (!chk) {
4206 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4207 goto error;
4208 }
4209 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004210 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004211
4212 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4213 "min-recv", "5", "ok-status", "L6OK",
4214 "error-status", "L6RSP", "tout-status", "L6TOUT",
4215 ""},
4216 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4217 if (!chk) {
4218 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4219 goto error;
4220 }
4221 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004222 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004223
4224 ruleset_found:
4225 rules->list = &rs->rules;
4226 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4227 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4228
4229 out:
4230 free(errmsg);
4231 return err_code;
4232
4233 error:
4234 free_tcpcheck_ruleset(rs);
4235 err_code |= ERR_ALERT | ERR_FATAL;
4236 goto out;
4237}
4238
4239/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004240int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004241 const char *file, int line)
4242{
4243 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4244
4245 struct tcpcheck_ruleset *rs = NULL;
4246 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4247 struct tcpcheck_rule *chk;
4248 struct tcpcheck_var *var = NULL;
4249 char *cmd = NULL, *errmsg = NULL;
4250 int err_code = 0;
4251
4252 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4253 err_code |= ERR_WARN;
4254
4255 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4256 goto out;
4257
4258 curpx->options2 &= ~PR_O2_CHK_ANY;
4259 curpx->options2 |= PR_O2_TCPCHK_CHK;
4260
4261 free_tcpcheck_vars(&rules->preset_vars);
4262 rules->list = NULL;
4263 rules->flags = 0;
4264
4265 cur_arg += 2;
4266 if (*args[cur_arg] && *args[cur_arg+1] &&
4267 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4268 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4269 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4270 if (cmd)
4271 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4272 }
4273 else {
4274 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4275 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4276 cmd = strdup("HELO localhost");
4277 }
4278
4279 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4280 if (cmd == NULL || var == NULL) {
4281 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4282 goto error;
4283 }
4284 var->data.type = SMP_T_STR;
4285 var->data.u.str.area = cmd;
4286 var->data.u.str.data = strlen(cmd);
4287 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004288 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004289 cmd = NULL;
4290 var = NULL;
4291
4292 rs = find_tcpcheck_ruleset("*smtp-check");
4293 if (rs)
4294 goto ruleset_found;
4295
4296 rs = create_tcpcheck_ruleset("*smtp-check");
4297 if (rs == NULL) {
4298 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4299 goto error;
4300 }
4301
4302 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4303 1, curpx, &rs->rules, file, line, &errmsg);
4304 if (!chk) {
4305 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4306 goto error;
4307 }
4308 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004309 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004310
4311 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4312 "min-recv", "4",
4313 "error-status", "L7RSP",
4314 "on-error", "%[res.payload(0,0),cut_crlf]",
4315 ""},
4316 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4317 if (!chk) {
4318 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4319 goto error;
4320 }
4321 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004322 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004323
4324 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4325 "min-recv", "4",
4326 "error-status", "L7STS",
4327 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4328 "status-code", "res.payload(0,3)",
4329 ""},
4330 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4331 if (!chk) {
4332 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4333 goto error;
4334 }
4335 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004336 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004337
4338 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4339 1, curpx, &rs->rules, file, line, &errmsg);
4340 if (!chk) {
4341 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4342 goto error;
4343 }
4344 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004345 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004346
Christopher Faulet96878442022-09-21 14:42:47 +02004347 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 +01004348 "error-status", "L7STS",
4349 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4350 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4351 "status-code", "res.payload(0,3)",
4352 ""},
4353 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4354 if (!chk) {
4355 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4356 goto error;
4357 }
4358 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004359 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004360
wrightlawd67b10f2022-09-08 16:10:48 +01004361 /* Send an SMTP QUIT to ensure clean disconnect (issue 1812), and expect a 2xx response code */
4362
4363 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "QUIT\r\n", ""},
4364 1, curpx, &rs->rules, file, line, &errmsg);
4365 if (!chk) {
4366 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4367 goto error;
4368 }
4369 chk->index = 5;
4370 LIST_APPEND(&rs->rules, &chk->list);
4371
4372 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4373 "min-recv", "4",
4374 "error-status", "L7STS",
4375 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4376 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4377 "status-code", "res.payload(0,3)",
4378 ""},
4379 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4380 if (!chk) {
4381 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4382 goto error;
4383 }
4384 chk->index = 6;
4385 LIST_APPEND(&rs->rules, &chk->list);
4386
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004387 ruleset_found:
4388 rules->list = &rs->rules;
4389 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4390 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4391
4392 out:
4393 free(errmsg);
4394 return err_code;
4395
4396 error:
4397 free(cmd);
4398 free(var);
4399 free_tcpcheck_vars(&rules->preset_vars);
4400 free_tcpcheck_ruleset(rs);
4401 err_code |= ERR_ALERT | ERR_FATAL;
4402 goto out;
4403}
4404
4405/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004406int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004407 const char *file, int line)
4408{
4409 static char pgsql_req[] = {
4410 "%[var(check.plen),htonl,hex]" /* The packet length*/
4411 "00030000" /* the version 3.0 */
4412 "7573657200" /* "user" key */
4413 "%[var(check.username),hex]00" /* the username */
4414 "00"
4415 };
4416
4417 struct tcpcheck_ruleset *rs = NULL;
4418 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4419 struct tcpcheck_rule *chk;
4420 struct tcpcheck_var *var = NULL;
4421 char *user = NULL, *errmsg = NULL;
4422 size_t packetlen = 0;
4423 int err_code = 0;
4424
4425 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4426 err_code |= ERR_WARN;
4427
4428 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4429 goto out;
4430
4431 curpx->options2 &= ~PR_O2_CHK_ANY;
4432 curpx->options2 |= PR_O2_TCPCHK_CHK;
4433
4434 free_tcpcheck_vars(&rules->preset_vars);
4435 rules->list = NULL;
4436 rules->flags = 0;
4437
4438 cur_arg += 2;
4439 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4440 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4441 file, line, args[0], args[1]);
4442 goto error;
4443 }
4444 if (strcmp(args[cur_arg], "user") == 0) {
4445 packetlen = 15 + strlen(args[cur_arg+1]);
4446 user = strdup(args[cur_arg+1]);
4447
4448 var = create_tcpcheck_var(ist("check.username"));
4449 if (user == NULL || var == NULL) {
4450 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4451 goto error;
4452 }
4453 var->data.type = SMP_T_STR;
4454 var->data.u.str.area = user;
4455 var->data.u.str.data = strlen(user);
4456 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004457 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004458 user = NULL;
4459 var = NULL;
4460
4461 var = create_tcpcheck_var(ist("check.plen"));
4462 if (var == NULL) {
4463 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4464 goto error;
4465 }
4466 var->data.type = SMP_T_SINT;
4467 var->data.u.sint = packetlen;
4468 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004469 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004470 var = NULL;
4471 }
4472 else {
4473 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4474 file, line, args[0], args[1]);
4475 goto error;
4476 }
4477
4478 rs = find_tcpcheck_ruleset("*pgsql-check");
4479 if (rs)
4480 goto ruleset_found;
4481
4482 rs = create_tcpcheck_ruleset("*pgsql-check");
4483 if (rs == NULL) {
4484 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4485 goto error;
4486 }
4487
4488 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4489 1, curpx, &rs->rules, file, line, &errmsg);
4490 if (!chk) {
4491 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4492 goto error;
4493 }
4494 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004495 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004496
4497 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4498 1, curpx, &rs->rules, file, line, &errmsg);
4499 if (!chk) {
4500 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4501 goto error;
4502 }
4503 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004504 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004505
4506 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4507 "min-recv", "5",
4508 "error-status", "L7RSP",
4509 "on-error", "%[res.payload(6,0)]",
4510 ""},
4511 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4512 if (!chk) {
4513 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4514 goto error;
4515 }
4516 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004517 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004518
Fatih Acar80cff2f2022-09-26 17:27:11 +02004519 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 +01004520 "min-recv", "9",
4521 "error-status", "L7STS",
4522 "on-success", "PostgreSQL server is ok",
4523 "on-error", "PostgreSQL unknown error",
4524 ""},
4525 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4526 if (!chk) {
4527 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4528 goto error;
4529 }
4530 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004531 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004532
4533 ruleset_found:
4534 rules->list = &rs->rules;
4535 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4536 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4537
4538 out:
4539 free(errmsg);
4540 return err_code;
4541
4542 error:
4543 free(user);
4544 free(var);
4545 free_tcpcheck_vars(&rules->preset_vars);
4546 free_tcpcheck_ruleset(rs);
4547 err_code |= ERR_ALERT | ERR_FATAL;
4548 goto out;
4549}
4550
4551
4552/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004553int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004554 const char *file, int line)
4555{
4556 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4557 * const char mysql40_client_auth_pkt[] = {
4558 * "\x0e\x00\x00" // packet length
4559 * "\x01" // packet number
4560 * "\x00\x00" // client capabilities
4561 * "\x00\x00\x01" // max packet
4562 * "haproxy\x00" // username (null terminated string)
4563 * "\x00" // filler (always 0x00)
4564 * "\x01\x00\x00" // packet length
4565 * "\x00" // packet number
4566 * "\x01" // COM_QUIT command
4567 * };
4568 */
4569 static char mysql40_rsname[] = "*mysql40-check";
4570 static char mysql40_req[] = {
4571 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4572 "0080" /* client capabilities */
4573 "000001" /* max packet */
4574 "%[var(check.username),hex]00" /* the username */
4575 "00" /* filler (always 0x00) */
4576 "010000" /* packet length*/
4577 "00" /* sequence ID */
4578 "01" /* COM_QUIT command */
4579 };
4580
4581 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4582 * const char mysql41_client_auth_pkt[] = {
4583 * "\x0e\x00\x00\" // packet length
4584 * "\x01" // packet number
4585 * "\x00\x00\x00\x00" // client capabilities
4586 * "\x00\x00\x00\x01" // max packet
4587 * "\x21" // character set (UTF-8)
4588 * char[23] // All zeroes
4589 * "haproxy\x00" // username (null terminated string)
4590 * "\x00" // filler (always 0x00)
4591 * "\x01\x00\x00" // packet length
4592 * "\x00" // packet number
4593 * "\x01" // COM_QUIT command
4594 * };
4595 */
4596 static char mysql41_rsname[] = "*mysql41-check";
4597 static char mysql41_req[] = {
4598 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4599 "00820000" /* client capabilities */
4600 "00800001" /* max packet */
4601 "21" /* character set (UTF-8) */
4602 "000000000000000000000000" /* 23 bytes, al zeroes */
4603 "0000000000000000000000"
4604 "%[var(check.username),hex]00" /* the username */
4605 "00" /* filler (always 0x00) */
4606 "010000" /* packet length*/
4607 "00" /* sequence ID */
4608 "01" /* COM_QUIT command */
4609 };
4610
4611 struct tcpcheck_ruleset *rs = NULL;
4612 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4613 struct tcpcheck_rule *chk;
4614 struct tcpcheck_var *var = NULL;
4615 char *mysql_rsname = "*mysql-check";
4616 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4617 int index = 0, err_code = 0;
4618
4619 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4620 err_code |= ERR_WARN;
4621
4622 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4623 goto out;
4624
4625 curpx->options2 &= ~PR_O2_CHK_ANY;
4626 curpx->options2 |= PR_O2_TCPCHK_CHK;
4627
4628 free_tcpcheck_vars(&rules->preset_vars);
4629 rules->list = NULL;
4630 rules->flags = 0;
4631
4632 cur_arg += 2;
4633 if (*args[cur_arg]) {
4634 int packetlen, userlen;
4635
4636 if (strcmp(args[cur_arg], "user") != 0) {
4637 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4638 file, line, args[0], args[1], args[cur_arg]);
4639 goto error;
4640 }
4641
4642 if (*(args[cur_arg+1]) == 0) {
4643 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4644 file, line, args[0], args[1], args[cur_arg]);
4645 goto error;
4646 }
4647
4648 hdr = calloc(4, sizeof(*hdr));
4649 user = strdup(args[cur_arg+1]);
4650 userlen = strlen(args[cur_arg+1]);
4651
4652 if (hdr == NULL || user == NULL) {
4653 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4654 goto error;
4655 }
4656
4657 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4658 packetlen = userlen + 7 + 27;
4659 mysql_req = mysql41_req;
4660 mysql_rsname = mysql41_rsname;
4661 }
4662 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4663 packetlen = userlen + 7;
4664 mysql_req = mysql40_req;
4665 mysql_rsname = mysql40_rsname;
4666 }
4667 else {
4668 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4669 file, line, args[cur_arg], args[cur_arg+2]);
4670 goto error;
4671 }
4672
4673 hdr[0] = (unsigned char)(packetlen & 0xff);
4674 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4675 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4676 hdr[3] = 1;
4677
4678 var = create_tcpcheck_var(ist("check.header"));
4679 if (var == NULL) {
4680 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4681 goto error;
4682 }
4683 var->data.type = SMP_T_STR;
4684 var->data.u.str.area = hdr;
4685 var->data.u.str.data = 4;
4686 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004687 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004688 hdr = NULL;
4689 var = NULL;
4690
4691 var = create_tcpcheck_var(ist("check.username"));
4692 if (var == NULL) {
4693 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4694 goto error;
4695 }
4696 var->data.type = SMP_T_STR;
4697 var->data.u.str.area = user;
4698 var->data.u.str.data = strlen(user);
4699 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004700 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004701 user = NULL;
4702 var = NULL;
4703 }
4704
4705 rs = find_tcpcheck_ruleset(mysql_rsname);
4706 if (rs)
4707 goto ruleset_found;
4708
4709 rs = create_tcpcheck_ruleset(mysql_rsname);
4710 if (rs == NULL) {
4711 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4712 goto error;
4713 }
4714
4715 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4716 1, curpx, &rs->rules, file, line, &errmsg);
4717 if (!chk) {
4718 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4719 goto error;
4720 }
4721 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004722 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004723
4724 if (mysql_req) {
4725 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4726 1, curpx, &rs->rules, file, line, &errmsg);
4727 if (!chk) {
4728 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4729 goto error;
4730 }
4731 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004732 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004733 }
4734
4735 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4736 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4737 if (!chk) {
4738 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4739 goto error;
4740 }
4741 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4742 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004743 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004744
4745 if (mysql_req) {
4746 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4747 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4748 if (!chk) {
4749 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4750 goto error;
4751 }
4752 chk->expect.custom = tcpcheck_mysql_expect_ok;
4753 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004754 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004755 }
4756
4757 ruleset_found:
4758 rules->list = &rs->rules;
4759 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4760 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4761
4762 out:
4763 free(errmsg);
4764 return err_code;
4765
4766 error:
4767 free(hdr);
4768 free(user);
4769 free(var);
4770 free_tcpcheck_vars(&rules->preset_vars);
4771 free_tcpcheck_ruleset(rs);
4772 err_code |= ERR_ALERT | ERR_FATAL;
4773 goto out;
4774}
4775
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004776int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004777 const char *file, int line)
4778{
4779 static char *ldap_req = "300C020101600702010304008000";
4780
4781 struct tcpcheck_ruleset *rs = NULL;
4782 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4783 struct tcpcheck_rule *chk;
4784 char *errmsg = NULL;
4785 int err_code = 0;
4786
4787 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4788 err_code |= ERR_WARN;
4789
4790 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4791 goto out;
4792
4793 curpx->options2 &= ~PR_O2_CHK_ANY;
4794 curpx->options2 |= PR_O2_TCPCHK_CHK;
4795
4796 free_tcpcheck_vars(&rules->preset_vars);
4797 rules->list = NULL;
4798 rules->flags = 0;
4799
4800 rs = find_tcpcheck_ruleset("*ldap-check");
4801 if (rs)
4802 goto ruleset_found;
4803
4804 rs = create_tcpcheck_ruleset("*ldap-check");
4805 if (rs == NULL) {
4806 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4807 goto error;
4808 }
4809
4810 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4811 1, curpx, &rs->rules, file, line, &errmsg);
4812 if (!chk) {
4813 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4814 goto error;
4815 }
4816 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004817 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004818
4819 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4820 "min-recv", "14",
4821 "on-error", "Not LDAPv3 protocol",
4822 ""},
4823 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4824 if (!chk) {
4825 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4826 goto error;
4827 }
4828 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004829 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004830
4831 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4832 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4833 if (!chk) {
4834 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4835 goto error;
4836 }
4837 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4838 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004839 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004840
4841 ruleset_found:
4842 rules->list = &rs->rules;
4843 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4844 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4845
4846 out:
4847 free(errmsg);
4848 return err_code;
4849
4850 error:
4851 free_tcpcheck_ruleset(rs);
4852 err_code |= ERR_ALERT | ERR_FATAL;
4853 goto out;
4854}
4855
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004856int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004857 const char *file, int line)
4858{
4859 struct tcpcheck_ruleset *rs = NULL;
4860 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4861 struct tcpcheck_rule *chk;
4862 char *spop_req = NULL;
4863 char *errmsg = NULL;
4864 int spop_len = 0, err_code = 0;
4865
4866 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4867 err_code |= ERR_WARN;
4868
4869 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4870 goto out;
4871
4872 curpx->options2 &= ~PR_O2_CHK_ANY;
4873 curpx->options2 |= PR_O2_TCPCHK_CHK;
4874
4875 free_tcpcheck_vars(&rules->preset_vars);
4876 rules->list = NULL;
4877 rules->flags = 0;
4878
4879
4880 rs = find_tcpcheck_ruleset("*spop-check");
4881 if (rs)
4882 goto ruleset_found;
4883
4884 rs = create_tcpcheck_ruleset("*spop-check");
4885 if (rs == NULL) {
4886 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4887 goto error;
4888 }
4889
4890 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4891 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4892 goto error;
4893 }
4894 chunk_reset(&trash);
4895 dump_binary(&trash, spop_req, spop_len);
4896 trash.area[trash.data] = '\0';
4897
4898 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4899 1, curpx, &rs->rules, file, line, &errmsg);
4900 if (!chk) {
4901 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4902 goto error;
4903 }
4904 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004905 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004906
4907 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4908 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4909 if (!chk) {
4910 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4911 goto error;
4912 }
4913 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4914 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004915 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004916
4917 ruleset_found:
4918 rules->list = &rs->rules;
4919 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4920 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4921
4922 out:
4923 free(spop_req);
4924 free(errmsg);
4925 return err_code;
4926
4927 error:
4928 free_tcpcheck_ruleset(rs);
4929 err_code |= ERR_ALERT | ERR_FATAL;
4930 goto out;
4931}
4932
4933
4934static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4935{
4936 struct tcpcheck_rule *chk = NULL;
4937 struct tcpcheck_http_hdr *hdr = NULL;
4938 char *meth = NULL, *uri = NULL, *vsn = NULL;
4939 char *hdrs, *body;
4940
4941 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4942 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4943 if (hdrs == body)
4944 hdrs = NULL;
4945 if (hdrs) {
4946 *hdrs = '\0';
4947 hdrs +=2;
4948 }
4949 if (body) {
4950 *body = '\0';
4951 body += 4;
4952 }
4953 if (hdrs || body) {
4954 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4955 " Please, consider to use 'http-check send' directive instead.");
4956 }
4957
4958 chk = calloc(1, sizeof(*chk));
4959 if (!chk) {
4960 memprintf(errmsg, "out of memory");
4961 goto error;
4962 }
4963 chk->action = TCPCHK_ACT_SEND;
4964 chk->send.type = TCPCHK_SEND_HTTP;
4965 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4966 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4967 LIST_INIT(&chk->send.http.hdrs);
4968
4969 /* Copy the method, uri and version */
4970 if (*args[cur_arg]) {
4971 if (!*args[cur_arg+1])
4972 uri = args[cur_arg];
4973 else
4974 meth = args[cur_arg];
4975 }
4976 if (*args[cur_arg+1])
4977 uri = args[cur_arg+1];
4978 if (*args[cur_arg+2])
4979 vsn = args[cur_arg+2];
4980
4981 if (meth) {
4982 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4983 chk->send.http.meth.str.area = strdup(meth);
4984 chk->send.http.meth.str.data = strlen(meth);
4985 if (!chk->send.http.meth.str.area) {
4986 memprintf(errmsg, "out of memory");
4987 goto error;
4988 }
4989 }
4990 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004991 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004992 if (!isttest(chk->send.http.uri)) {
4993 memprintf(errmsg, "out of memory");
4994 goto error;
4995 }
4996 }
4997 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004998 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004999 if (!isttest(chk->send.http.vsn)) {
5000 memprintf(errmsg, "out of memory");
5001 goto error;
5002 }
5003 }
5004
5005 /* Copy the header */
5006 if (hdrs) {
5007 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
5008 struct h1m h1m;
5009 int i, ret;
5010
5011 /* Build and parse the request */
5012 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
5013
5014 h1m.flags = H1_MF_HDRS_ONLY;
5015 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
5016 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
5017 &h1m, NULL);
5018 if (ret <= 0) {
5019 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
5020 goto error;
5021 }
5022
5023 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
5024 hdr = calloc(1, sizeof(*hdr));
5025 if (!hdr) {
5026 memprintf(errmsg, "out of memory");
5027 goto error;
5028 }
5029 LIST_INIT(&hdr->value);
5030 hdr->name = istdup(tmp_hdrs[i].n);
5031 if (!hdr->name.ptr) {
5032 memprintf(errmsg, "out of memory");
5033 goto error;
5034 }
5035
5036 ist0(tmp_hdrs[i].v);
5037 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5038 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005039 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005040 }
5041 }
5042
5043 /* Copy the body */
5044 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005045 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005046 if (!isttest(chk->send.http.body)) {
5047 memprintf(errmsg, "out of memory");
5048 goto error;
5049 }
5050 }
5051
5052 return chk;
5053
5054 error:
5055 free_tcpcheck_http_hdr(hdr);
5056 free_tcpcheck(chk, 0);
5057 return NULL;
5058}
5059
5060/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005061int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005062 const char *file, int line)
5063{
5064 struct tcpcheck_ruleset *rs = NULL;
5065 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5066 struct tcpcheck_rule *chk;
5067 char *errmsg = NULL;
5068 int err_code = 0;
5069
5070 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5071 err_code |= ERR_WARN;
5072
5073 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5074 goto out;
5075
5076 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5077 if (!chk) {
5078 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5079 goto error;
5080 }
5081 if (errmsg) {
5082 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5083 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005084 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005085 }
5086
5087 no_request:
5088 curpx->options2 &= ~PR_O2_CHK_ANY;
5089 curpx->options2 |= PR_O2_TCPCHK_CHK;
5090
5091 free_tcpcheck_vars(&rules->preset_vars);
5092 rules->list = NULL;
5093 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5094
5095 /* Deduce the ruleset name from the proxy info */
5096 chunk_printf(&trash, "*http-check-%s_%s-%d",
5097 ((curpx == defpx) ? "defaults" : curpx->id),
5098 curpx->conf.file, curpx->conf.line);
5099
5100 rs = find_tcpcheck_ruleset(b_orig(&trash));
5101 if (rs == NULL) {
5102 rs = create_tcpcheck_ruleset(b_orig(&trash));
5103 if (rs == NULL) {
5104 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5105 goto error;
5106 }
5107 }
5108
5109 rules->list = &rs->rules;
5110 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5111 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5112 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5113 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5114 rules->list = NULL;
5115 goto error;
5116 }
5117
5118 out:
5119 free(errmsg);
5120 return err_code;
5121
5122 error:
5123 free_tcpcheck_ruleset(rs);
5124 free_tcpcheck(chk, 0);
5125 err_code |= ERR_ALERT | ERR_FATAL;
5126 goto out;
5127}
5128
5129/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005130int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005131 const char *file, int line)
5132{
5133 struct tcpcheck_ruleset *rs = NULL;
5134 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5135 int err_code = 0;
5136
5137 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5138 err_code |= ERR_WARN;
5139
5140 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5141 goto out;
5142
5143 curpx->options2 &= ~PR_O2_CHK_ANY;
5144 curpx->options2 |= PR_O2_TCPCHK_CHK;
5145
5146 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5147 /* If a tcp-check rulesset is already set, do nothing */
5148 if (rules->list)
5149 goto out;
5150
5151 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5152 * get it.
5153 */
5154 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5155 goto curpx_ruleset;
5156
5157 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5158 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5159 rs = find_tcpcheck_ruleset(b_orig(&trash));
5160 if (rs)
5161 goto ruleset_found;
5162 }
5163
5164 curpx_ruleset:
5165 /* Deduce the ruleset name from the proxy info */
5166 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5167 ((curpx == defpx) ? "defaults" : curpx->id),
5168 curpx->conf.file, curpx->conf.line);
5169
5170 rs = find_tcpcheck_ruleset(b_orig(&trash));
5171 if (rs == NULL) {
5172 rs = create_tcpcheck_ruleset(b_orig(&trash));
5173 if (rs == NULL) {
5174 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5175 goto error;
5176 }
5177 }
5178
5179 ruleset_found:
5180 free_tcpcheck_vars(&rules->preset_vars);
5181 rules->list = &rs->rules;
5182 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5183 rules->flags |= TCPCHK_RULES_TCP_CHK;
5184
5185 out:
5186 return err_code;
5187
5188 error:
5189 err_code |= ERR_ALERT | ERR_FATAL;
5190 goto out;
5191}
5192
Willy Tarreau51cd5952020-06-05 12:25:38 +02005193static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005194 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005195 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5196 { 0, NULL, NULL },
5197}};
5198
5199REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5200REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5201REGISTER_POST_DEINIT(deinit_tcpchecks);
5202INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);