blob: 288bd19d75b1966db699d671b3fa73886700315c [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>
wrightlaw9a8d8a32022-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>
Willy Tarreau51cd5952020-06-05 12:25:38 +020028#include <signal.h>
29#include <stdarg.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <time.h>
34#include <unistd.h>
35
36#include <haproxy/action.h>
37#include <haproxy/api.h>
38#include <haproxy/cfgparse.h>
39#include <haproxy/check.h>
40#include <haproxy/chunk.h>
41#include <haproxy/connection.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020042#include <haproxy/errors.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020043#include <haproxy/global.h>
44#include <haproxy/h1.h>
45#include <haproxy/http.h>
46#include <haproxy/http_htx.h>
47#include <haproxy/htx.h>
48#include <haproxy/istbuf.h>
49#include <haproxy/list.h>
50#include <haproxy/log.h>
Christopher Faulet8a0e5f82021-09-16 16:01:09 +020051#include <haproxy/net_helper.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020052#include <haproxy/protocol.h>
53#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020054#include <haproxy/regex.h>
55#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020056#include <haproxy/server.h>
57#include <haproxy/ssl_sock.h>
Willy Tarreaucb086c62022-05-27 09:47:12 +020058#include <haproxy/stconn.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020059#include <haproxy/task.h>
60#include <haproxy/tcpcheck.h>
Willy Tarreau9310f482021-10-06 16:18:40 +020061#include <haproxy/ticks.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020062#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)) {
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100433 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200434 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))
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100521 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200522 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 Faulet8a0e5f82021-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 Faulet8a0e5f82021-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 Faulet8a0e5f82021-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 Faulet8a0e5f82021-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 Faulet8a0e5f82021-09-16 16:01:09 +0200708 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200709
Christopher Faulet8a0e5f82021-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 Faulet8a0e5f82021-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 Faulet8a0e5f82021-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 Faulet8a0e5f82021-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 Faulet8a0e5f82021-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 */
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200825 const char *sc = NULL; /* maxconn */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200826 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) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200935 sc = cmd;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200936 }
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
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200980 if (sc) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200981 const char *msg;
982
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200983 sc += strlen("maxconn:");
Willy Tarreau51cd5952020-06-05 12:25:38 +0200984
Christopher Faulet147b8c92021-04-10 09:00:38 +0200985 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury Denoyelle02742862021-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 Tarreaubde14ad2022-05-27 10:04:04 +0200989 msg = server_parse_maxconn_change_request(check->server, sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200990 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;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001060 struct connection *conn = sc_conn(check->sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001061 struct protocol *proto;
1062 struct xprt_ops *xprt;
1063 struct tcpcheck_rule *next;
1064 int status, port;
1065
Christopher Faulet147b8c92021-04-10 09:00:38 +02001066 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1067
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001068 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1069
1070 /* current connection already created, check if it is established or not */
1071 if (conn) {
1072 if (conn->flags & CO_FL_WAIT_XPRT) {
1073 /* We are still waiting for the connection establishment */
1074 if (next && next->action == TCPCHK_ACT_SEND) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001075 if (!(check->sc->wait_event.events & SUB_RETRY_SEND))
1076 conn->mux->subscribe(check->sc, SUB_RETRY_SEND, &check->sc->wait_event);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001077 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001078 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001079 }
1080 else
1081 ret = tcpcheck_eval_recv(check, rule);
1082 }
1083 goto out;
1084 }
1085
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001086 /* Note: here check->sc = sc = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001087
Christopher Fauletb381a502020-11-25 13:47:00 +01001088 /* Always release input and output buffer when a new connect is evaluated */
1089 check_release_buf(check, &check->bi);
1090 check_release_buf(check, &check->bo);
1091
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001092 /* No connection, prepare a new one */
Christopher Fauletdd2d0d82021-12-20 09:34:32 +01001093 conn = conn_new((s ? &s->obj_type : &proxy->obj_type));
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001094 if (!conn) {
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001095 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1096 tcpcheck_get_step_id(check, rule));
1097 if (rule->comment)
1098 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1099 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1100 ret = TCPCHK_EVAL_STOP;
Willy Tarreau4596fe22022-05-17 19:07:51 +02001101 TRACE_ERROR("stconn allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001102 goto out;
1103 }
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001104 if (sc_attach_mux(check->sc, NULL, conn) < 0) {
Christopher Faulet070b91b2022-03-31 19:27:18 +02001105 TRACE_ERROR("mux attach error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
1106 conn_free(conn);
1107 conn = NULL;
1108 status = SF_ERR_RESOURCE;
1109 goto fail_check;
1110 }
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001111 conn->ctx = check->sc;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001112 conn_set_owner(conn, check->sess, NULL);
1113
Willy Tarreau51cd5952020-06-05 12:25:38 +02001114 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001115 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001116 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001117 status = SF_ERR_RESOURCE;
1118 goto fail_check;
1119 }
1120
1121 /* connect to the connect rule addr if specified, otherwise the check
1122 * addr if specified on the server. otherwise, use the server addr (it
1123 * MUST exist at this step).
1124 */
1125 *conn->dst = (is_addr(&connect->addr)
1126 ? connect->addr
1127 : (is_addr(&check->addr) ? check->addr : s->addr));
Willy Tarreau14e7f292021-10-27 17:41:07 +02001128 proto = protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001129
1130 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001131 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001132 port = connect->port;
1133 if (!port && connect->port_expr) {
1134 struct sample *smp;
1135
1136 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1137 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1138 connect->port_expr, SMP_T_SINT);
1139 if (smp)
1140 port = smp->data.u.sint;
1141 }
1142 if (!port && is_inet_addr(&connect->addr))
1143 port = get_host_port(&connect->addr);
1144 if (!port && check->port)
1145 port = check->port;
1146 if (!port && is_inet_addr(&check->addr))
1147 port = get_host_port(&check->addr);
1148 if (!port) {
1149 /* The server MUST exist here */
1150 port = s->svc_port;
1151 }
1152 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001153 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001154
1155 xprt = ((connect->options & TCPCHK_OPT_SSL)
1156 ? xprt_get(XPRT_SSL)
1157 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1158
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001159 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001160 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001161 status = SF_ERR_RESOURCE;
1162 goto fail_check;
1163 }
1164
Christopher Fauletf7177272020-10-02 13:41:55 +02001165 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1166 conn->send_proxy_ofs = 1;
1167 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001168 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001169 }
1170 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1171 conn->send_proxy_ofs = 1;
1172 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001173 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001174 }
1175
1176 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1177 conn->send_proxy_ofs = 1;
1178 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001179 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001180 }
1181 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1182 conn->send_proxy_ofs = 1;
1183 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001184 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001185 }
1186
Willy Tarreau51cd5952020-06-05 12:25:38 +02001187 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001188 if (proto && proto->connect) {
1189 int flags = 0;
1190
Christopher Fauletf6112482022-08-30 10:31:15 +02001191 if (!next)
1192 flags |= CONNECT_DELACK_ALWAYS;
Christopher Faulet871dd822022-08-24 11:38:03 +02001193 if (connect->options & TCPCHK_OPT_HAS_DATA)
Christopher Fauletf6112482022-08-30 10:31:15 +02001194 flags |= (CONNECT_HAS_DATA|CONNECT_DELACK_ALWAYS);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001195 status = proto->connect(conn, flags);
1196 }
1197
1198 if (status != SF_ERR_NONE)
1199 goto fail_check;
1200
Christopher Faulet21ddc742020-07-01 15:26:14 +02001201 conn_set_private(conn);
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001202 conn->ctx = check->sc;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001203
Willy Tarreau51cd5952020-06-05 12:25:38 +02001204#ifdef USE_OPENSSL
1205 if (connect->sni)
1206 ssl_sock_set_servername(conn, connect->sni);
1207 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1208 ssl_sock_set_servername(conn, s->check.sni);
1209
1210 if (connect->alpn)
1211 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1212 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1213 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1214#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001215
Willy Tarreaue2226792022-04-11 18:04:33 +02001216 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER) && !(conn->flags & CO_FL_FDLESS)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001217 /* Some servers don't like reset on close */
Christopher Faulet897d6122021-12-17 17:28:35 +01001218 HA_ATOMIC_AND(&fdtab[conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001219 }
1220
1221 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1222 if (xprt_add_hs(conn) < 0)
1223 status = SF_ERR_RESOURCE;
1224 }
1225
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001226 if (conn_xprt_start(conn) < 0) {
1227 status = SF_ERR_RESOURCE;
1228 goto fail_check;
1229 }
1230
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001231 /* The mux may be initialized now if there isn't server attached to the
1232 * check (email alerts) or if there is a mux proto specified or if there
1233 * is no alpn.
1234 */
1235 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1236 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1237 const struct mux_ops *mux_ops;
1238
Christopher Faulet147b8c92021-04-10 09:00:38 +02001239 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001240 if (connect->mux_proto)
1241 mux_ops = connect->mux_proto->mux;
1242 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1243 mux_ops = check->mux_proto->mux;
1244 else {
1245 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1246 ? PROTO_MODE_HTTP
1247 : PROTO_MODE_TCP);
1248
1249 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1250 }
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001251 if (mux_ops && conn_install_mux(conn, mux_ops, check->sc, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001252 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001253 status = SF_ERR_INTERNAL;
1254 goto fail_check;
1255 }
1256 }
1257
Willy Tarreau51cd5952020-06-05 12:25:38 +02001258 fail_check:
1259 /* It can return one of :
1260 * - SF_ERR_NONE if everything's OK
1261 * - SF_ERR_SRVTO if there are no more servers
1262 * - SF_ERR_SRVCL if the connection was refused by the server
1263 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1264 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1265 * - SF_ERR_INTERNAL for any other purely internal errors
1266 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1267 * Note that we try to prevent the network stack from sending the ACK during the
1268 * connect() when a pure TCP check is used (without PROXY protocol).
1269 */
1270 switch (status) {
1271 case SF_ERR_NONE:
1272 /* we allow up to min(inter, timeout.connect) for a connection
1273 * to establish but only when timeout.check is set as it may be
1274 * to short for a full check otherwise
1275 */
1276 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1277
1278 if (proxy->timeout.check && proxy->timeout.connect) {
1279 int t_con = tick_add(now_ms, proxy->timeout.connect);
1280 t->expire = tick_first(t->expire, t_con);
1281 }
1282 break;
1283 case SF_ERR_SRVTO: /* ETIMEDOUT */
1284 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1285 case SF_ERR_PRXCOND:
1286 case SF_ERR_RESOURCE:
1287 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001288 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 +02001289 chk_report_conn_err(check, errno, 0);
1290 ret = TCPCHK_EVAL_STOP;
1291 goto out;
1292 }
1293
1294 /* don't do anything until the connection is established */
1295 if (conn->flags & CO_FL_WAIT_XPRT) {
1296 if (conn->mux) {
1297 if (next && next->action == TCPCHK_ACT_SEND)
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001298 conn->mux->subscribe(check->sc, SUB_RETRY_SEND, &check->sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001299 else
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001300 conn->mux->subscribe(check->sc, SUB_RETRY_RECV, &check->sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001301 }
1302 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001303 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001304 goto out;
1305 }
1306
1307 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001308 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001309 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001310 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001311 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001312
1313 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1314 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1315
Christopher Faulet147b8c92021-04-10 09:00:38 +02001316 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001317 return ret;
1318}
1319
1320/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1321 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1322 * TCPCHK_EVAL_STOP if an error occurred.
1323 */
1324enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1325{
1326 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1327 struct tcpcheck_send *send = &rule->send;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001328 struct stconn *sc = check->sc;
1329 struct connection *conn = __sc_conn(sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001330 struct buffer *tmp = NULL;
1331 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001332 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001333
Christopher Faulet147b8c92021-04-10 09:00:38 +02001334 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1335
Christopher Fauletb381a502020-11-25 13:47:00 +01001336 if (check->state & CHK_ST_OUT_ALLOC) {
1337 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001338 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 +01001339 goto out;
1340 }
1341
1342 if (!check_get_buf(check, &check->bo)) {
1343 check->state |= CHK_ST_OUT_ALLOC;
1344 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001345 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 +01001346 goto out;
1347 }
1348
Christopher Faulet39066c22020-11-25 13:34:51 +01001349 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001350 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 +02001351 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 +01001352 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001353 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001354
Christopher Fauletb381a502020-11-25 13:47:00 +01001355 /* Always release input buffer when a new send is evaluated */
1356 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001357
1358 switch (send->type) {
1359 case TCPCHK_SEND_STRING:
1360 case TCPCHK_SEND_BINARY:
1361 if (istlen(send->data) >= b_size(&check->bo)) {
1362 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1363 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1364 tcpcheck_get_step_id(check, rule));
1365 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1366 ret = TCPCHK_EVAL_STOP;
1367 goto out;
1368 }
1369 b_putist(&check->bo, send->data);
1370 break;
1371 case TCPCHK_SEND_STRING_LF:
1372 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1373 if (!b_data(&check->bo))
1374 goto out;
1375 break;
1376 case TCPCHK_SEND_BINARY_LF: {
1377 int len = b_size(&check->bo);
1378
1379 tmp = alloc_trash_chunk();
1380 if (!tmp)
1381 goto error_lf;
1382 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1383 if (!b_data(tmp))
1384 goto out;
1385 tmp->area[tmp->data] = '\0';
1386 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1387 goto error_lf;
1388 check->bo.data = len;
1389 break;
1390 }
1391 case TCPCHK_SEND_HTTP: {
1392 struct htx_sl *sl;
1393 struct ist meth, uri, vsn, clen, body;
1394 unsigned int slflags = 0;
1395
1396 tmp = alloc_trash_chunk();
1397 if (!tmp)
1398 goto error_htx;
1399
1400 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1401 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1402 : http_known_methods[send->http.meth.meth]);
1403 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1404 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1405 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1406 }
1407 else
1408 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1409 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1410
1411 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1412 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1413 slflags |= HTX_SL_F_VER_11;
1414 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
Christopher Faulet0506d9d2023-02-28 18:44:14 +01001415 if (!(send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !isttest(send->http.body))
Willy Tarreau51cd5952020-06-05 12:25:38 +02001416 slflags |= HTX_SL_F_BODYLESS;
1417
1418 htx = htx_from_buf(&check->bo);
1419 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1420 if (!sl)
1421 goto error_htx;
1422 sl->info.req.meth = send->http.meth.meth;
1423 if (!http_update_host(htx, sl, uri))
1424 goto error_htx;
1425
1426 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1427 struct tcpcheck_http_hdr *hdr;
1428 struct ist hdr_value;
1429
1430 list_for_each_entry(hdr, &send->http.hdrs, list) {
1431 chunk_reset(tmp);
1432 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1433 if (!b_data(tmp))
1434 continue;
1435 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1436 if (!htx_add_header(htx, hdr->name, hdr_value))
1437 goto error_htx;
1438 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1439 if (!http_update_authority(htx, sl, hdr_value))
1440 goto error_htx;
1441 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001442 if (isteqi(hdr->name, ist("connection")))
1443 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001444 }
1445
1446 }
1447 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1448 chunk_reset(tmp);
1449 httpchk_build_status_header(check->server, tmp);
1450 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1451 goto error_htx;
1452 }
1453
1454
1455 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1456 chunk_reset(tmp);
1457 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1458 body = ist2(b_orig(tmp), b_data(tmp));
1459 }
1460 else
1461 body = send->http.body;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001462
Christopher Fauletd48bfb62023-02-28 18:51:26 +01001463 if (!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close")))
Willy Tarreau51cd5952020-06-05 12:25:38 +02001464 goto error_htx;
1465
Christopher Fauletd48bfb62023-02-28 18:51:26 +01001466 if ((send->http.meth.meth != HTTP_METH_OPTIONS &&
1467 send->http.meth.meth != HTTP_METH_GET &&
1468 send->http.meth.meth != HTTP_METH_HEAD &&
1469 send->http.meth.meth != HTTP_METH_DELETE) || istlen(body)) {
1470 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1471 if (!htx_add_header(htx, ist("Content-length"), clen))
1472 goto error_htx;
1473 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001474
1475 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001476 (istlen(body) && !htx_add_data_atonce(htx, body)))
1477 goto error_htx;
1478
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001479 /* no more data are expected */
1480 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001481 htx_to_buf(htx, &check->bo);
1482 break;
1483 }
1484 case TCPCHK_SEND_UNDEF:
1485 /* Should never happen. */
1486 ret = TCPCHK_EVAL_STOP;
1487 goto out;
1488 };
1489
Christopher Faulet39066c22020-11-25 13:34:51 +01001490 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001491 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001492 if (conn->mux->snd_buf(sc, &check->bo,
Willy Tarreau51cd5952020-06-05 12:25:38 +02001493 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001494 if ((conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001495 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001496 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 +02001497 goto out;
1498 }
1499 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001500 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001501 conn->mux->subscribe(sc, SUB_RETRY_SEND, &sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001502 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001503 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001504 goto out;
1505 }
1506
1507 out:
1508 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001509 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1510 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001511
1512 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001513 return ret;
1514
1515 error_htx:
1516 if (htx) {
1517 htx_reset(htx);
1518 htx_to_buf(htx, &check->bo);
1519 }
1520 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1521 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001522 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 +02001523 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1524 ret = TCPCHK_EVAL_STOP;
1525 goto out;
1526
1527 error_lf:
1528 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1529 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001530 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 +02001531 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1532 ret = TCPCHK_EVAL_STOP;
1533 goto out;
1534
1535}
1536
1537/* Try to receive data before evaluating a tcp-check expect rule. Returns
1538 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1539 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1540 * TCPCHK_EVAL_STOP if an error occurred.
1541 */
1542enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1543{
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001544 struct stconn *sc = check->sc;
1545 struct connection *conn = __sc_conn(sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001546 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1547 size_t max, read, cur_read = 0;
1548 int is_empty;
1549 int read_poll = MAX_READ_POLL_LOOPS;
1550
Christopher Faulet147b8c92021-04-10 09:00:38 +02001551 TRACE_ENTER(CHK_EV_RX_DATA, check);
1552
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001553 if (sc->wait_event.events & SUB_RETRY_RECV) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001554 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001555 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001556 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001557
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001558 if (sc_ep_test(sc, SE_FL_EOS))
Willy Tarreau51cd5952020-06-05 12:25:38 +02001559 goto end_recv;
1560
Christopher Faulet147b8c92021-04-10 09:00:38 +02001561 if (check->state & CHK_ST_IN_ALLOC) {
1562 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001563 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001564 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001565
1566 if (!check_get_buf(check, &check->bi)) {
1567 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001568 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001569 goto wait_more_data;
1570 }
1571
Willy Tarreau4596fe22022-05-17 19:07:51 +02001572 /* errors on the connection and the stream connector were already checked */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001573
1574 /* prepare to detect if the mux needs more room */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001575 sc_ep_clr(sc, SE_FL_WANT_ROOM);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001576
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001577 while (sc_ep_test(sc, SE_FL_RCV_MORE) ||
1578 (!(conn->flags & CO_FL_ERROR) && !sc_ep_test(sc, SE_FL_ERROR | SE_FL_EOS))) {
1579 max = (IS_HTX_SC(sc) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1580 read = conn->mux->rcv_buf(sc, &check->bi, max, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001581 cur_read += read;
1582 if (!read ||
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001583 sc_ep_test(sc, SE_FL_WANT_ROOM) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001584 (--read_poll <= 0) ||
1585 (read < max && read >= global.tune.recv_enough))
1586 break;
1587 }
1588
1589 end_recv:
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001590 is_empty = (IS_HTX_SC(sc) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1591 if (is_empty && ((conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001592 /* Report network errors only if we got no other data. Otherwise
1593 * we'll let the upper layers decide whether the response is OK
1594 * or not. It is very common that an RST sent by the server is
1595 * reported as an error just after the last data chunk.
1596 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001597 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001598 goto stop;
1599 }
1600 if (!cur_read) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001601 if (sc_ep_test(sc, SE_FL_EOI)) {
Christopher Fauletd16e7dd2021-10-20 13:53:38 +02001602 /* If EOI is set, it means there is a response or an error */
1603 goto out;
1604 }
Willy Tarreau0cfcc402022-05-17 16:10:17 +02001605
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001606 if (!sc_ep_test(sc, SE_FL_WANT_ROOM | SE_FL_ERROR | SE_FL_EOS)) {
1607 conn->mux->subscribe(sc, SUB_RETRY_RECV, &sc->wait_event);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001608 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001609 goto wait_more_data;
1610 }
Willy Tarreau0cfcc402022-05-17 16:10:17 +02001611
Willy Tarreau51cd5952020-06-05 12:25:38 +02001612 if (is_empty) {
1613 int status;
1614
1615 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1616 tcpcheck_get_step_id(check, rule));
1617 if (rule->comment)
1618 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1619
Christopher Faulet147b8c92021-04-10 09:00:38 +02001620 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001621 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1622 set_server_check_status(check, status, trash.area);
1623 goto stop;
1624 }
1625 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001626 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001627
1628 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001629 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1630 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001631
1632 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001633 return ret;
1634
1635 stop:
1636 ret = TCPCHK_EVAL_STOP;
1637 goto out;
1638
1639 wait_more_data:
1640 ret = TCPCHK_EVAL_WAIT;
1641 goto out;
1642}
1643
1644/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1645 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1646 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1647 * error occurred.
1648 */
1649enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1650{
1651 struct htx *htx = htxbuf(&check->bi);
1652 struct htx_sl *sl;
1653 struct htx_blk *blk;
1654 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1655 struct tcpcheck_expect *expect = &rule->expect;
1656 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1657 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1658 struct ist desc = IST_NULL;
1659 int i, match, inverse;
1660
Christopher Faulet147b8c92021-04-10 09:00:38 +02001661 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1662
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001663 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001664
1665 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001666 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001667 status = HCHK_STATUS_L7RSP;
1668 goto error;
1669 }
1670
1671 if (htx_is_empty(htx)) {
1672 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001673 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001674 status = HCHK_STATUS_L7RSP;
1675 goto error;
1676 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001677 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001678 goto wait_more_data;
1679 }
1680
1681 sl = http_get_stline(htx);
1682 check->code = sl->info.res.status;
1683
1684 if (check->server &&
1685 (check->server->proxy->options & PR_O_DISABLE404) &&
1686 (check->server->next_state != SRV_ST_STOPPED) &&
1687 (check->code == 404)) {
1688 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001689 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001690 goto out;
1691 }
1692
1693 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1694 /* Make GCC happy ; initialize match to a failure state. */
1695 match = inverse;
1696 status = expect->err_status;
1697
1698 switch (expect->type) {
1699 case TCPCHK_EXPECT_HTTP_STATUS:
1700 match = 0;
1701 for (i = 0; i < expect->codes.num; i++) {
1702 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1703 sl->info.res.status <= expect->codes.codes[i][1]) {
1704 match = 1;
1705 break;
1706 }
1707 }
1708
1709 /* Set status and description in case of error */
1710 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1711 if (LIST_ISEMPTY(&expect->onerror_fmt))
1712 desc = htx_sl_res_reason(sl);
1713 break;
1714 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1715 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1716
1717 /* Set status and description in case of error */
1718 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1719 if (LIST_ISEMPTY(&expect->onerror_fmt))
1720 desc = htx_sl_res_reason(sl);
1721 break;
1722
1723 case TCPCHK_EXPECT_HTTP_HEADER: {
1724 struct http_hdr_ctx ctx;
1725 struct ist npat, vpat, value;
1726 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1727
1728 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1729 nbuf = alloc_trash_chunk();
1730 if (!nbuf) {
1731 status = HCHK_STATUS_L7RSP;
1732 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001733 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001734 goto error;
1735 }
1736 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1737 if (!b_data(nbuf)) {
1738 status = HCHK_STATUS_L7RSP;
1739 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001740 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001741 goto error;
1742 }
1743 npat = ist2(b_orig(nbuf), b_data(nbuf));
1744 }
1745 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1746 npat = expect->hdr.name;
1747
1748 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1749 vbuf = alloc_trash_chunk();
1750 if (!vbuf) {
1751 status = HCHK_STATUS_L7RSP;
1752 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001753 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001754 goto error;
1755 }
1756 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1757 if (!b_data(vbuf)) {
1758 status = HCHK_STATUS_L7RSP;
1759 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001760 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001761 goto error;
1762 }
1763 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1764 }
1765 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1766 vpat = expect->hdr.value;
1767
1768 match = 0;
1769 ctx.blk = NULL;
1770 while (1) {
1771 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1772 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1773 if (!http_find_str_header(htx, npat, &ctx, full))
1774 goto end_of_match;
1775 break;
1776 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1777 if (!http_find_pfx_header(htx, npat, &ctx, full))
1778 goto end_of_match;
1779 break;
1780 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1781 if (!http_find_sfx_header(htx, npat, &ctx, full))
1782 goto end_of_match;
1783 break;
1784 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1785 if (!http_find_sub_header(htx, npat, &ctx, full))
1786 goto end_of_match;
1787 break;
1788 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1789 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1790 goto end_of_match;
1791 break;
1792 default:
1793 /* should never happen */
1794 goto end_of_match;
1795 }
1796
1797 /* A header has matched the name pattern, let's test its
1798 * value now (always defined from there). If there is no
1799 * value pattern, it is a good match.
1800 */
1801
1802 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1803 match = 1;
1804 goto end_of_match;
1805 }
1806
1807 value = ctx.value;
1808 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1809 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1810 if (isteq(value, vpat)) {
1811 match = 1;
1812 goto end_of_match;
1813 }
1814 break;
1815 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1816 if (istlen(value) < istlen(vpat))
1817 break;
1818 value = ist2(istptr(value), istlen(vpat));
1819 if (isteq(value, vpat)) {
1820 match = 1;
1821 goto end_of_match;
1822 }
1823 break;
1824 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1825 if (istlen(value) < istlen(vpat))
1826 break;
Tim Duesterhus4c8f75f2021-11-06 15:14:44 +01001827 value = ist2(istend(value) - istlen(vpat), istlen(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001828 if (isteq(value, vpat)) {
1829 match = 1;
1830 goto end_of_match;
1831 }
1832 break;
1833 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1834 if (isttest(istist(value, vpat))) {
1835 match = 1;
1836 goto end_of_match;
1837 }
1838 break;
1839 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1840 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1841 match = 1;
1842 goto end_of_match;
1843 }
1844 break;
1845 }
1846 }
1847
1848 end_of_match:
1849 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1850 if (LIST_ISEMPTY(&expect->onerror_fmt))
1851 desc = htx_sl_res_reason(sl);
1852 break;
1853 }
1854
1855 case TCPCHK_EXPECT_HTTP_BODY:
1856 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1857 case TCPCHK_EXPECT_HTTP_BODY_LF:
1858 match = 0;
1859 chunk_reset(&trash);
1860 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1861 enum htx_blk_type type = htx_get_blk_type(blk);
1862
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001863 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001864 break;
1865 if (type == HTX_BLK_DATA) {
1866 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1867 break;
1868 }
1869 }
1870
1871 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001872 if (!last_read) {
1873 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001874 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001875 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001876 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1877 if (LIST_ISEMPTY(&expect->onerror_fmt))
1878 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001879 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001880 goto error;
1881 }
1882
1883 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1884 tmp = alloc_trash_chunk();
1885 if (!tmp) {
1886 status = HCHK_STATUS_L7RSP;
1887 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001888 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001889 goto error;
1890 }
1891 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1892 if (!b_data(tmp)) {
1893 status = HCHK_STATUS_L7RSP;
1894 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001895 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001896 goto error;
1897 }
1898 }
1899
1900 if (!last_read &&
1901 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1902 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1903 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1904 ret = TCPCHK_EVAL_WAIT;
1905 goto out;
1906 }
1907
1908 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1909 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1910 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1911 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1912 else
1913 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1914
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001915 /* Wait for more data on mismatch only if no minimum is defined (-1),
1916 * otherwise the absence of match is already conclusive.
1917 */
1918 if (!match && !last_read && (expect->min_recv == -1)) {
1919 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001920 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001921 goto out;
1922 }
1923
Willy Tarreau51cd5952020-06-05 12:25:38 +02001924 /* Set status and description in case of error */
1925 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1926 if (LIST_ISEMPTY(&expect->onerror_fmt))
1927 desc = (inverse
1928 ? ist("HTTP check matched unwanted content")
1929 : ist("HTTP content check did not match"));
1930 break;
1931
1932
1933 default:
1934 /* should never happen */
1935 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1936 goto error;
1937 }
1938
Christopher Faulet147b8c92021-04-10 09:00:38 +02001939 if (!(match ^ inverse)) {
1940 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001941 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001942 }
1943
1944 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001945
1946 out:
1947 free_trash_chunk(tmp);
1948 free_trash_chunk(nbuf);
1949 free_trash_chunk(vbuf);
1950 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001951 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001952 return ret;
1953
1954 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001955 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001956 ret = TCPCHK_EVAL_STOP;
1957 msg = alloc_trash_chunk();
1958 if (msg)
1959 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1960 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1961 goto out;
1962
1963 wait_more_data:
1964 ret = TCPCHK_EVAL_WAIT;
1965 goto out;
1966}
1967
1968/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1969 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1970 * if an error occurred.
1971 */
1972enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1973{
1974 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1975 struct tcpcheck_expect *expect = &rule->expect;
1976 struct buffer *msg = NULL, *tmp = NULL;
1977 struct ist desc = IST_NULL;
1978 enum healthcheck_status status;
1979 int match, inverse;
1980
Christopher Faulet147b8c92021-04-10 09:00:38 +02001981 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1982
Willy Tarreau51cd5952020-06-05 12:25:38 +02001983 last_read |= b_full(&check->bi);
1984
1985 /* The current expect might need more data than the previous one, check again
1986 * that the minimum amount data required to match is respected.
1987 */
1988 if (!last_read) {
1989 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1990 (b_data(&check->bi) < istlen(expect->data))) {
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 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1996 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001997 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001998 goto out;
1999 }
2000 }
2001
2002 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2003 /* Make GCC happy ; initialize match to a failure state. */
2004 match = inverse;
2005 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
2006
2007 switch (expect->type) {
2008 case TCPCHK_EXPECT_STRING:
2009 case TCPCHK_EXPECT_BINARY:
2010 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2011 break;
2012 case TCPCHK_EXPECT_STRING_REGEX:
2013 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2014 break;
2015
2016 case TCPCHK_EXPECT_BINARY_REGEX:
2017 chunk_reset(&trash);
2018 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2019 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2020 break;
2021
2022 case TCPCHK_EXPECT_STRING_LF:
2023 case TCPCHK_EXPECT_BINARY_LF:
2024 match = 0;
2025 tmp = alloc_trash_chunk();
2026 if (!tmp) {
2027 status = HCHK_STATUS_L7RSP;
2028 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002029 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002030 goto error;
2031 }
2032 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2033 if (!b_data(tmp)) {
2034 status = HCHK_STATUS_L7RSP;
2035 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002036 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002037 goto error;
2038 }
2039 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2040 int len = tmp->data;
2041 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2042 status = HCHK_STATUS_L7RSP;
2043 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002044 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002045 goto error;
2046 }
2047 tmp->data = len;
2048 }
2049 if (b_data(&check->bi) < tmp->data) {
2050 if (!last_read) {
2051 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002052 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002053 goto out;
2054 }
2055 break;
2056 }
2057 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2058 break;
2059
2060 case TCPCHK_EXPECT_CUSTOM:
2061 if (expect->custom)
2062 ret = expect->custom(check, rule, last_read);
2063 goto out;
2064 default:
2065 /* Should never happen. */
2066 ret = TCPCHK_EVAL_STOP;
2067 goto out;
2068 }
2069
2070
2071 /* Wait for more data on mismatch only if no minimum is defined (-1),
2072 * otherwise the absence of match is already conclusive.
2073 */
2074 if (!match && !last_read && (expect->min_recv == -1)) {
2075 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002076 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002077 goto out;
2078 }
2079
2080 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002081 if (match ^ inverse) {
2082 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002083 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002084 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002085
2086 error:
2087 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002088 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002089 ret = TCPCHK_EVAL_STOP;
2090 msg = alloc_trash_chunk();
2091 if (msg)
2092 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2093 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2094 free_trash_chunk(msg);
2095
2096 out:
2097 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002098 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002099 return ret;
2100}
2101
2102/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2103 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2104 * waits.
2105 */
2106enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2107{
2108 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2109 struct act_rule *act_rule;
2110 enum act_return act_ret;
2111
2112 act_rule =rule->action_kw.rule;
2113 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2114 if (act_ret != ACT_RET_CONT) {
2115 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2116 tcpcheck_get_step_id(check, rule));
2117 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2118 ret = TCPCHK_EVAL_STOP;
2119 }
2120
2121 return ret;
2122}
2123
2124/* Executes a tcp-check ruleset. Note that this is called both from the
2125 * connection's wake() callback and from the check scheduling task. It returns
2126 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2127 * presenting the risk of an fd replacement.
2128 *
2129 * Please do NOT place any return statement in this function and only leave
2130 * via the out_end_tcpcheck label after setting retcode.
2131 */
2132int tcpcheck_main(struct check *check)
2133{
2134 struct tcpcheck_rule *rule;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002135 struct stconn *sc = check->sc;
2136 struct connection *conn = sc_conn(sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002137 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002138 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002139 enum tcpcheck_eval_ret eval_ret;
2140
2141 /* here, we know that the check is complete or that it failed */
2142 if (check->result != CHK_RES_UNKNOWN)
2143 goto out;
2144
Christopher Faulet147b8c92021-04-10 09:00:38 +02002145 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2146
Willy Tarreau4596fe22022-05-17 19:07:51 +02002147 /* Note: the stream connector and the connection may only be undefined before
Willy Tarreau51cd5952020-06-05 12:25:38 +02002148 * the first rule evaluation (it is always a connect rule) or when the
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002149 * stream connector allocation failed on a connect rule, during sc allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002150 */
2151
2152 /* 1- check for connection error, if any */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002153 if ((conn && conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002154 goto out_end_tcpcheck;
2155
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002156 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002157 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002158 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002159 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002160 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2161 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002162
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002163 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002164 * tcp-check variables */
2165 else {
2166 struct tcpcheck_var *var;
2167
2168 /* First evaluation, create a session */
2169 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2170 if (!check->sess) {
2171 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002172 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002173 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2174 goto out_end_tcpcheck;
2175 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002176 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002177 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2178
2179 /* Preset tcp-check variables */
2180 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2181 struct sample smp;
2182
2183 memset(&smp, 0, sizeof(smp));
2184 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2185 smp.data = var->data;
2186 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2187 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002188 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002189 }
2190
2191 /* Now evaluate the tcp-check rules */
2192
2193 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2194 check->code = 0;
2195 switch (rule->action) {
2196 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002197 /* Not the first connection, release it first */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002198 if (sc_conn(sc) && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002199 check->state |= CHK_ST_CLOSE_CONN;
2200 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002201 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002202
2203 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002204
2205 /* We are still waiting the connection gets closed */
Christopher Faulet560b8da2022-05-30 08:37:39 +02002206 if (check->state & CHK_ST_CLOSE_CONN) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002207 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002208 eval_ret = TCPCHK_EVAL_WAIT;
2209 break;
2210 }
2211
Christopher Faulet147b8c92021-04-10 09:00:38 +02002212 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002213 eval_ret = tcpcheck_eval_connect(check, rule);
2214
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002215 /* Refresh connection */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002216 conn = sc_conn(sc);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002217 last_read = 0;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002218 must_read = (IS_HTX_SC(sc) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002219 break;
2220 case TCPCHK_ACT_SEND:
2221 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002222 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002223 eval_ret = tcpcheck_eval_send(check, rule);
2224 must_read = 1;
2225 break;
2226 case TCPCHK_ACT_EXPECT:
2227 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002228 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002229 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002230 eval_ret = tcpcheck_eval_recv(check, rule);
2231 if (eval_ret == TCPCHK_EVAL_STOP)
2232 goto out_end_tcpcheck;
2233 else if (eval_ret == TCPCHK_EVAL_WAIT)
2234 goto out;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002235 last_read = ((conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR | SE_FL_EOS));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002236 must_read = 0;
2237 }
2238
2239 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2240 ? tcpcheck_eval_expect_http(check, rule, last_read)
2241 : tcpcheck_eval_expect(check, rule, last_read));
2242
2243 if (eval_ret == TCPCHK_EVAL_WAIT) {
2244 check->current_step = rule->expect.head;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002245 if (!(sc->wait_event.events & SUB_RETRY_RECV))
2246 conn->mux->subscribe(sc, SUB_RETRY_RECV, &sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002247 }
2248 break;
2249 case TCPCHK_ACT_ACTION_KW:
2250 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002251 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002252 eval_ret = tcpcheck_eval_action_kw(check, rule);
2253 break;
2254 default:
2255 /* Otherwise, just go to the next one and don't update
2256 * the current step
2257 */
2258 eval_ret = TCPCHK_EVAL_CONTINUE;
2259 break;
2260 }
2261
2262 switch (eval_ret) {
2263 case TCPCHK_EVAL_CONTINUE:
2264 break;
2265 case TCPCHK_EVAL_WAIT:
2266 goto out;
2267 case TCPCHK_EVAL_STOP:
2268 goto out_end_tcpcheck;
2269 }
2270 }
2271
2272 /* All rules was evaluated */
2273 if (check->current_step) {
2274 rule = check->current_step;
2275
Christopher Faulet147b8c92021-04-10 09:00:38 +02002276 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2277
Willy Tarreau51cd5952020-06-05 12:25:38 +02002278 if (rule->action == TCPCHK_ACT_EXPECT) {
2279 struct buffer *msg;
2280 enum healthcheck_status status;
2281
2282 if (check->server &&
2283 (check->server->proxy->options & PR_O_DISABLE404) &&
2284 (check->server->next_state != SRV_ST_STOPPED) &&
2285 (check->code == 404)) {
2286 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002287 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002288 goto out_end_tcpcheck;
2289 }
2290
2291 msg = alloc_trash_chunk();
2292 if (msg)
2293 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2294 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2295 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2296 free_trash_chunk(msg);
2297 }
2298 else if (rule->action == TCPCHK_ACT_CONNECT) {
2299 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2300 enum healthcheck_status status = HCHK_STATUS_L4OK;
2301#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002302 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002303 status = HCHK_STATUS_L6OK;
2304#endif
2305 set_server_check_status(check, status, msg);
2306 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002307 else
2308 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002309 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002310 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002311 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002312 }
2313 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002314
2315 out_end_tcpcheck:
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002316 if ((conn && conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002317 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002318 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002319 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002320
Christopher Fauletb381a502020-11-25 13:47:00 +01002321 /* the tcpcheck is finished, release in/out buffer now */
2322 check_release_buf(check, &check->bi);
2323 check_release_buf(check, &check->bo);
2324
Willy Tarreau51cd5952020-06-05 12:25:38 +02002325 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002326 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002327 return retcode;
2328}
2329
Willy Tarreaua631b862022-03-02 14:54:44 +01002330void tcp_check_keywords_register(struct action_kw_list *kw_list)
2331{
2332 LIST_APPEND(&tcp_check_keywords.list, &kw_list->list);
2333}
Willy Tarreau51cd5952020-06-05 12:25:38 +02002334
2335/**************************************************************************/
2336/******************* Internals to parse tcp-check rules *******************/
2337/**************************************************************************/
2338struct action_kw_list tcp_check_keywords = {
2339 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2340};
2341
2342/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2343 * returned on error.
2344 */
2345struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2346 struct list *rules, struct action_kw *kw,
2347 const char *file, int line, char **errmsg)
2348{
2349 struct tcpcheck_rule *chk = NULL;
2350 struct act_rule *actrule = NULL;
2351
Willy Tarreaud535f802021-10-11 08:49:26 +02002352 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002353 if (!actrule) {
2354 memprintf(errmsg, "out of memory");
2355 goto error;
2356 }
2357 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002358
2359 cur_arg++;
2360 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2361 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2362 goto error;
2363 }
2364
2365 chk = calloc(1, sizeof(*chk));
2366 if (!chk) {
2367 memprintf(errmsg, "out of memory");
2368 goto error;
2369 }
2370 chk->action = TCPCHK_ACT_ACTION_KW;
2371 chk->action_kw.rule = actrule;
2372 return chk;
2373
2374 error:
2375 free(actrule);
2376 return NULL;
2377}
2378
2379/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2380 * returned on error.
2381 */
2382struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2383 const char *file, int line, char **errmsg)
2384{
2385 struct tcpcheck_rule *chk = NULL;
2386 struct sockaddr_storage *sk = NULL;
2387 char *comment = NULL, *sni = NULL, *alpn = NULL;
2388 struct sample_expr *port_expr = NULL;
2389 const struct mux_proto_list *mux_proto = NULL;
2390 unsigned short conn_opts = 0;
2391 long port = 0;
2392 int alpn_len = 0;
2393
2394 list_for_each_entry(chk, rules, list) {
2395 if (chk->action == TCPCHK_ACT_CONNECT)
2396 break;
2397 if (chk->action == TCPCHK_ACT_COMMENT ||
2398 chk->action == TCPCHK_ACT_ACTION_KW ||
2399 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2400 continue;
2401
2402 memprintf(errmsg, "first step MUST also be a 'connect', "
2403 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2404 "when there is a 'connect' step in the tcp-check ruleset");
2405 goto error;
2406 }
2407
2408 cur_arg++;
2409 while (*(args[cur_arg])) {
2410 if (strcmp(args[cur_arg], "default") == 0)
2411 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2412 else if (strcmp(args[cur_arg], "addr") == 0) {
2413 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002414
2415 if (!*(args[cur_arg+1])) {
2416 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2417 goto error;
2418 }
2419
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002420 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2421 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002422 if (!sk) {
2423 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2424 goto error;
2425 }
2426
Willy Tarreau51cd5952020-06-05 12:25:38 +02002427 cur_arg++;
2428 }
2429 else if (strcmp(args[cur_arg], "port") == 0) {
2430 const char *p, *end;
2431
2432 if (!*(args[cur_arg+1])) {
2433 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2434 goto error;
2435 }
2436 cur_arg++;
2437
2438 port = 0;
2439 release_sample_expr(port_expr);
2440 p = args[cur_arg]; end = p + strlen(p);
2441 port = read_uint(&p, end);
2442 if (p != end) {
2443 int idx = 0;
2444
2445 px->conf.args.ctx = ARGC_SRV;
2446 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002447 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002448
2449 if (!port_expr) {
2450 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2451 goto error;
2452 }
2453 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2454 memprintf(errmsg, "error detected while parsing port expression : "
2455 " fetch method '%s' extracts information from '%s', "
2456 "none of which is available here.\n",
2457 args[cur_arg], sample_src_names(port_expr->fetch->use));
2458 goto error;
2459 }
2460 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2461 }
2462 else if (port > 65535 || port < 1) {
2463 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2464 args[cur_arg]);
2465 goto error;
2466 }
2467 }
2468 else if (strcmp(args[cur_arg], "proto") == 0) {
2469 if (!*(args[cur_arg+1])) {
2470 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2471 goto error;
2472 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002473 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002474 if (!mux_proto) {
2475 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2476 goto error;
2477 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002478
2479 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2480 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2481 goto error;
2482 }
2483 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2484 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2485 goto error;
2486 }
2487
Willy Tarreau51cd5952020-06-05 12:25:38 +02002488 cur_arg++;
2489 }
2490 else if (strcmp(args[cur_arg], "comment") == 0) {
2491 if (!*(args[cur_arg+1])) {
2492 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2493 goto error;
2494 }
2495 cur_arg++;
2496 free(comment);
2497 comment = strdup(args[cur_arg]);
2498 if (!comment) {
2499 memprintf(errmsg, "out of memory");
2500 goto error;
2501 }
2502 }
2503 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2504 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2505 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2506 conn_opts |= TCPCHK_OPT_SOCKS4;
2507 else if (strcmp(args[cur_arg], "linger") == 0)
2508 conn_opts |= TCPCHK_OPT_LINGER;
2509#ifdef USE_OPENSSL
2510 else if (strcmp(args[cur_arg], "ssl") == 0) {
2511 px->options |= PR_O_TCPCHK_SSL;
2512 conn_opts |= TCPCHK_OPT_SSL;
2513 }
2514 else if (strcmp(args[cur_arg], "sni") == 0) {
2515 if (!*(args[cur_arg+1])) {
2516 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2517 goto error;
2518 }
2519 cur_arg++;
2520 free(sni);
2521 sni = strdup(args[cur_arg]);
2522 if (!sni) {
2523 memprintf(errmsg, "out of memory");
2524 goto error;
2525 }
2526 }
2527 else if (strcmp(args[cur_arg], "alpn") == 0) {
2528#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2529 free(alpn);
2530 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2531 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2532 goto error;
2533 }
2534 cur_arg++;
2535#else
2536 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2537 goto error;
2538#endif
2539 }
2540#endif /* USE_OPENSSL */
2541
2542 else {
2543 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2544#ifdef USE_OPENSSL
2545 ", 'ssl', 'sni', 'alpn'"
2546#endif /* USE_OPENSSL */
2547 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2548 args[cur_arg]);
2549 goto error;
2550 }
2551 cur_arg++;
2552 }
2553
2554 chk = calloc(1, sizeof(*chk));
2555 if (!chk) {
2556 memprintf(errmsg, "out of memory");
2557 goto error;
2558 }
2559 chk->action = TCPCHK_ACT_CONNECT;
2560 chk->comment = comment;
2561 chk->connect.port = port;
2562 chk->connect.options = conn_opts;
2563 chk->connect.sni = sni;
2564 chk->connect.alpn = alpn;
2565 chk->connect.alpn_len= alpn_len;
2566 chk->connect.port_expr= port_expr;
2567 chk->connect.mux_proto= mux_proto;
2568 if (sk)
2569 chk->connect.addr = *sk;
2570 return chk;
2571
2572 error:
2573 free(alpn);
2574 free(sni);
2575 free(comment);
2576 release_sample_expr(port_expr);
2577 return NULL;
2578}
2579
2580/* Parses and creates a tcp-check send rule. NULL is returned on error */
2581struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2582 const char *file, int line, char **errmsg)
2583{
2584 struct tcpcheck_rule *chk = NULL;
2585 char *comment = NULL, *data = NULL;
2586 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2587
2588 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2589 type = TCPCHK_SEND_BINARY_LF;
2590 else if (strcmp(args[cur_arg], "send-binary") == 0)
2591 type = TCPCHK_SEND_BINARY;
2592 else if (strcmp(args[cur_arg], "send-lf") == 0)
2593 type = TCPCHK_SEND_STRING_LF;
2594 else if (strcmp(args[cur_arg], "send") == 0)
2595 type = TCPCHK_SEND_STRING;
2596
2597 if (!*(args[cur_arg+1])) {
2598 memprintf(errmsg, "'%s' expects a %s as argument",
2599 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2600 goto error;
2601 }
2602
2603 data = args[cur_arg+1];
2604
2605 cur_arg += 2;
2606 while (*(args[cur_arg])) {
2607 if (strcmp(args[cur_arg], "comment") == 0) {
2608 if (!*(args[cur_arg+1])) {
2609 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2610 goto error;
2611 }
2612 cur_arg++;
2613 free(comment);
2614 comment = strdup(args[cur_arg]);
2615 if (!comment) {
2616 memprintf(errmsg, "out of memory");
2617 goto error;
2618 }
2619 }
2620 else {
2621 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2622 args[cur_arg]);
2623 goto error;
2624 }
2625 cur_arg++;
2626 }
2627
2628 chk = calloc(1, sizeof(*chk));
2629 if (!chk) {
2630 memprintf(errmsg, "out of memory");
2631 goto error;
2632 }
2633 chk->action = TCPCHK_ACT_SEND;
2634 chk->comment = comment;
2635 chk->send.type = type;
2636
2637 switch (chk->send.type) {
2638 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002639 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002640 if (!isttest(chk->send.data)) {
2641 memprintf(errmsg, "out of memory");
2642 goto error;
2643 }
2644 break;
2645 case TCPCHK_SEND_BINARY: {
2646 int len = chk->send.data.len;
2647 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2648 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2649 goto error;
2650 }
2651 chk->send.data.len = len;
2652 break;
2653 }
2654 case TCPCHK_SEND_STRING_LF:
2655 case TCPCHK_SEND_BINARY_LF:
2656 LIST_INIT(&chk->send.fmt);
2657 px->conf.args.ctx = ARGC_SRV;
2658 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2659 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2660 goto error;
2661 }
2662 break;
2663 case TCPCHK_SEND_HTTP:
2664 case TCPCHK_SEND_UNDEF:
2665 goto error;
2666 }
2667
2668 return chk;
2669
2670 error:
2671 free(chk);
2672 free(comment);
2673 return NULL;
2674}
2675
2676/* Parses and creates a http-check send rule. NULL is returned on error */
2677struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2678 const char *file, int line, char **errmsg)
2679{
2680 struct tcpcheck_rule *chk = NULL;
2681 struct tcpcheck_http_hdr *hdr = NULL;
2682 struct http_hdr hdrs[global.tune.max_http_hdr];
2683 char *meth = NULL, *uri = NULL, *vsn = NULL;
2684 char *body = NULL, *comment = NULL;
2685 unsigned int flags = 0;
2686 int i = 0, host_hdr = -1;
2687
2688 cur_arg++;
2689 while (*(args[cur_arg])) {
2690 if (strcmp(args[cur_arg], "meth") == 0) {
2691 if (!*(args[cur_arg+1])) {
2692 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2693 goto error;
2694 }
2695 cur_arg++;
2696 meth = args[cur_arg];
2697 }
2698 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2699 if (!*(args[cur_arg+1])) {
2700 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2701 goto error;
2702 }
2703 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2704 if (strcmp(args[cur_arg], "uri-lf") == 0)
2705 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2706 cur_arg++;
2707 uri = args[cur_arg];
2708 }
2709 else if (strcmp(args[cur_arg], "ver") == 0) {
2710 if (!*(args[cur_arg+1])) {
2711 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2712 goto error;
2713 }
2714 cur_arg++;
2715 vsn = args[cur_arg];
2716 }
2717 else if (strcmp(args[cur_arg], "hdr") == 0) {
2718 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2719 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2720 goto error;
2721 }
2722
2723 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2724 if (host_hdr >= 0) {
2725 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2726 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2727 goto error;
2728 }
2729 host_hdr = i;
2730 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002731 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002732 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2733 goto skip_hdr;
2734
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002735 hdrs[i].n = ist(args[cur_arg + 1]);
2736 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002737 i++;
2738 skip_hdr:
2739 cur_arg += 2;
2740 }
2741 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2742 if (!*(args[cur_arg+1])) {
2743 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2744 goto error;
2745 }
2746 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2747 if (strcmp(args[cur_arg], "body-lf") == 0)
2748 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2749 cur_arg++;
2750 body = args[cur_arg];
2751 }
2752 else if (strcmp(args[cur_arg], "comment") == 0) {
2753 if (!*(args[cur_arg+1])) {
2754 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2755 goto error;
2756 }
2757 cur_arg++;
2758 free(comment);
2759 comment = strdup(args[cur_arg]);
2760 if (!comment) {
2761 memprintf(errmsg, "out of memory");
2762 goto error;
2763 }
2764 }
2765 else {
2766 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2767 " but got '%s' as argument.", args[cur_arg]);
2768 goto error;
2769 }
2770 cur_arg++;
2771 }
2772
2773 hdrs[i].n = hdrs[i].v = IST_NULL;
2774
2775 chk = calloc(1, sizeof(*chk));
2776 if (!chk) {
2777 memprintf(errmsg, "out of memory");
2778 goto error;
2779 }
2780 chk->action = TCPCHK_ACT_SEND;
2781 chk->comment = comment; comment = NULL;
2782 chk->send.type = TCPCHK_SEND_HTTP;
2783 chk->send.http.flags = flags;
2784 LIST_INIT(&chk->send.http.hdrs);
2785
2786 if (meth) {
2787 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2788 chk->send.http.meth.str.area = strdup(meth);
2789 chk->send.http.meth.str.data = strlen(meth);
2790 if (!chk->send.http.meth.str.area) {
2791 memprintf(errmsg, "out of memory");
2792 goto error;
2793 }
2794 }
2795 if (uri) {
2796 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2797 LIST_INIT(&chk->send.http.uri_fmt);
2798 px->conf.args.ctx = ARGC_SRV;
2799 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2800 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2801 goto error;
2802 }
2803 }
2804 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002805 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002806 if (!isttest(chk->send.http.uri)) {
2807 memprintf(errmsg, "out of memory");
2808 goto error;
2809 }
2810 }
2811 }
2812 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002813 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002814 if (!isttest(chk->send.http.vsn)) {
2815 memprintf(errmsg, "out of memory");
2816 goto error;
2817 }
2818 }
2819 for (i = 0; istlen(hdrs[i].n); i++) {
2820 hdr = calloc(1, sizeof(*hdr));
2821 if (!hdr) {
2822 memprintf(errmsg, "out of memory");
2823 goto error;
2824 }
2825 LIST_INIT(&hdr->value);
2826 hdr->name = istdup(hdrs[i].n);
2827 if (!isttest(hdr->name)) {
2828 memprintf(errmsg, "out of memory");
2829 goto error;
2830 }
2831
2832 ist0(hdrs[i].v);
2833 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2834 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002835 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002836 hdr = NULL;
2837 }
2838
2839 if (body) {
2840 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2841 LIST_INIT(&chk->send.http.body_fmt);
2842 px->conf.args.ctx = ARGC_SRV;
2843 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2844 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2845 goto error;
2846 }
2847 }
2848 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002849 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002850 if (!isttest(chk->send.http.body)) {
2851 memprintf(errmsg, "out of memory");
2852 goto error;
2853 }
2854 }
2855 }
2856
2857 return chk;
2858
2859 error:
2860 free_tcpcheck_http_hdr(hdr);
2861 free_tcpcheck(chk, 0);
2862 free(comment);
2863 return NULL;
2864}
2865
2866/* Parses and creates a http-check comment rule. NULL is returned on error */
2867struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2868 const char *file, int line, char **errmsg)
2869{
2870 struct tcpcheck_rule *chk = NULL;
2871 char *comment = NULL;
2872
2873 if (!*(args[cur_arg+1])) {
2874 memprintf(errmsg, "expects a string as argument");
2875 goto error;
2876 }
2877 cur_arg++;
2878 comment = strdup(args[cur_arg]);
2879 if (!comment) {
2880 memprintf(errmsg, "out of memory");
2881 goto error;
2882 }
2883
2884 chk = calloc(1, sizeof(*chk));
2885 if (!chk) {
2886 memprintf(errmsg, "out of memory");
2887 goto error;
2888 }
2889 chk->action = TCPCHK_ACT_COMMENT;
2890 chk->comment = comment;
2891 return chk;
2892
2893 error:
2894 free(comment);
2895 return NULL;
2896}
2897
2898/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2899 * on error. <proto> is set to the right protocol flags (covered by the
2900 * TCPCHK_RULES_PROTO_CHK mask).
2901 */
2902struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2903 struct list *rules, unsigned int proto,
2904 const char *file, int line, char **errmsg)
2905{
2906 struct tcpcheck_rule *prev_check, *chk = NULL;
2907 struct sample_expr *status_expr = NULL;
2908 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2909 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2910 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2911 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2912 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2913 unsigned int flags = 0;
2914 long min_recv = -1;
2915 int inverse = 0;
2916
2917 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2918 if (!*(args[cur_arg+1])) {
2919 memprintf(errmsg, "expects at least a matching pattern as arguments");
2920 goto error;
2921 }
2922
2923 cur_arg++;
2924 while (*(args[cur_arg])) {
2925 int in_pattern = 0;
2926
2927 rescan:
2928 if (strcmp(args[cur_arg], "min-recv") == 0) {
2929 if (in_pattern) {
2930 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2931 goto error;
2932 }
2933 if (!*(args[cur_arg+1])) {
2934 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2935 goto error;
2936 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002937 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002938 cur_arg++;
2939 min_recv = atol(args[cur_arg]);
2940 if (min_recv < -1 || min_recv > INT_MAX) {
2941 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2942 goto error;
2943 }
2944 }
2945 else if (*(args[cur_arg]) == '!') {
2946 in_pattern = 1;
2947 while (*(args[cur_arg]) == '!') {
2948 inverse = !inverse;
2949 args[cur_arg]++;
2950 }
2951 if (!*(args[cur_arg]))
2952 cur_arg++;
2953 goto rescan;
2954 }
2955 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2956 if (type != TCPCHK_EXPECT_UNDEF) {
2957 memprintf(errmsg, "only on pattern expected");
2958 goto error;
2959 }
2960 if (proto != TCPCHK_RULES_HTTP_CHK)
2961 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2962 else
2963 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2964
2965 if (!*(args[cur_arg+1])) {
2966 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2967 goto error;
2968 }
2969 cur_arg++;
2970 pattern = args[cur_arg];
2971 }
2972 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2973 if (proto == TCPCHK_RULES_HTTP_CHK)
2974 goto bad_http_kw;
2975 if (type != TCPCHK_EXPECT_UNDEF) {
2976 memprintf(errmsg, "only on pattern expected");
2977 goto error;
2978 }
2979 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2980
2981 if (!*(args[cur_arg+1])) {
2982 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2983 goto error;
2984 }
2985 cur_arg++;
2986 pattern = args[cur_arg];
2987 }
2988 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2989 if (type != TCPCHK_EXPECT_UNDEF) {
2990 memprintf(errmsg, "only on pattern expected");
2991 goto error;
2992 }
2993 if (proto != TCPCHK_RULES_HTTP_CHK)
2994 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2995 else {
2996 if (*(args[cur_arg]) != 's')
2997 goto bad_http_kw;
2998 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2999 }
3000
3001 if (!*(args[cur_arg+1])) {
3002 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3003 goto error;
3004 }
3005 cur_arg++;
3006 pattern = args[cur_arg];
3007 }
3008 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3009 if (proto != TCPCHK_RULES_HTTP_CHK)
3010 goto bad_tcp_kw;
3011 if (type != TCPCHK_EXPECT_UNDEF) {
3012 memprintf(errmsg, "only on pattern expected");
3013 goto error;
3014 }
3015 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3016
3017 if (!*(args[cur_arg+1])) {
3018 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3019 goto error;
3020 }
3021 cur_arg++;
3022 pattern = args[cur_arg];
3023 }
3024 else if (strcmp(args[cur_arg], "custom") == 0) {
3025 if (in_pattern) {
3026 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3027 goto error;
3028 }
3029 if (type != TCPCHK_EXPECT_UNDEF) {
3030 memprintf(errmsg, "only on pattern expected");
3031 goto error;
3032 }
3033 type = TCPCHK_EXPECT_CUSTOM;
3034 }
3035 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3036 int orig_arg = cur_arg;
3037
3038 if (proto != TCPCHK_RULES_HTTP_CHK)
3039 goto bad_tcp_kw;
3040 if (type != TCPCHK_EXPECT_UNDEF) {
3041 memprintf(errmsg, "only on pattern expected");
3042 goto error;
3043 }
3044 type = TCPCHK_EXPECT_HTTP_HEADER;
3045
3046 if (strcmp(args[cur_arg], "fhdr") == 0)
3047 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3048
3049 /* Parse the name pattern, mandatory */
3050 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3051 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3052 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3053 args[orig_arg]);
3054 goto error;
3055 }
3056
3057 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3058 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3059
3060 cur_arg += 2;
3061 if (strcmp(args[cur_arg], "-m") == 0) {
3062 if (!*(args[cur_arg+1])) {
3063 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3064 args[orig_arg], args[cur_arg]);
3065 goto error;
3066 }
3067 if (strcmp(args[cur_arg+1], "str") == 0)
3068 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3069 else if (strcmp(args[cur_arg+1], "beg") == 0)
3070 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3071 else if (strcmp(args[cur_arg+1], "end") == 0)
3072 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3073 else if (strcmp(args[cur_arg+1], "sub") == 0)
3074 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3075 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3076 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3077 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3078 args[orig_arg]);
3079 goto error;
3080 }
3081 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3082 }
3083 else {
3084 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3085 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3086 goto error;
3087 }
3088 cur_arg += 2;
3089 }
3090 else
3091 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3092 npat = args[cur_arg];
3093
3094 if (!*(args[cur_arg+1]) ||
3095 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3096 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3097 goto next;
3098 }
3099 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3100 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3101
3102 /* Parse the value pattern, optional */
3103 if (strcmp(args[cur_arg+2], "-m") == 0) {
3104 cur_arg += 2;
3105 if (!*(args[cur_arg+1])) {
3106 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3107 args[orig_arg], args[cur_arg]);
3108 goto error;
3109 }
3110 if (strcmp(args[cur_arg+1], "str") == 0)
3111 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3112 else if (strcmp(args[cur_arg+1], "beg") == 0)
3113 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3114 else if (strcmp(args[cur_arg+1], "end") == 0)
3115 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3116 else if (strcmp(args[cur_arg+1], "sub") == 0)
3117 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3118 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3119 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3120 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3121 args[orig_arg]);
3122 goto error;
3123 }
3124 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3125 }
3126 else {
3127 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3128 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3129 goto error;
3130 }
3131 }
3132 else
3133 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3134
3135 if (!*(args[cur_arg+2])) {
3136 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3137 goto error;
3138 }
3139 vpat = args[cur_arg+2];
3140 cur_arg += 2;
3141 }
3142 else if (strcmp(args[cur_arg], "comment") == 0) {
3143 if (in_pattern) {
3144 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3145 goto error;
3146 }
3147 if (!*(args[cur_arg+1])) {
3148 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3149 goto error;
3150 }
3151 cur_arg++;
3152 free(comment);
3153 comment = strdup(args[cur_arg]);
3154 if (!comment) {
3155 memprintf(errmsg, "out of memory");
3156 goto error;
3157 }
3158 }
3159 else if (strcmp(args[cur_arg], "on-success") == 0) {
3160 if (in_pattern) {
3161 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3162 goto error;
3163 }
3164 if (!*(args[cur_arg+1])) {
3165 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3166 goto error;
3167 }
3168 cur_arg++;
3169 on_success_msg = args[cur_arg];
3170 }
3171 else if (strcmp(args[cur_arg], "on-error") == 0) {
3172 if (in_pattern) {
3173 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3174 goto error;
3175 }
3176 if (!*(args[cur_arg+1])) {
3177 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3178 goto error;
3179 }
3180 cur_arg++;
3181 on_error_msg = args[cur_arg];
3182 }
3183 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3184 if (in_pattern) {
3185 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3186 goto error;
3187 }
3188 if (!*(args[cur_arg+1])) {
3189 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3190 goto error;
3191 }
3192 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3193 ok_st = HCHK_STATUS_L7OKD;
3194 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3195 ok_st = HCHK_STATUS_L7OKCD;
3196 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3197 ok_st = HCHK_STATUS_L6OK;
3198 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3199 ok_st = HCHK_STATUS_L4OK;
3200 else {
3201 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3202 args[cur_arg], args[cur_arg+1]);
3203 goto error;
3204 }
3205 cur_arg++;
3206 }
3207 else if (strcmp(args[cur_arg], "error-status") == 0) {
3208 if (in_pattern) {
3209 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3210 goto error;
3211 }
3212 if (!*(args[cur_arg+1])) {
3213 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3214 goto error;
3215 }
3216 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3217 err_st = HCHK_STATUS_L7RSP;
3218 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3219 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003220 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3221 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003222 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3223 err_st = HCHK_STATUS_L6RSP;
3224 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3225 err_st = HCHK_STATUS_L4CON;
3226 else {
3227 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3228 args[cur_arg], args[cur_arg+1]);
3229 goto error;
3230 }
3231 cur_arg++;
3232 }
3233 else if (strcmp(args[cur_arg], "status-code") == 0) {
3234 int idx = 0;
3235
3236 if (in_pattern) {
3237 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3238 goto error;
3239 }
3240 if (!*(args[cur_arg+1])) {
3241 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3242 goto error;
3243 }
3244
3245 cur_arg++;
3246 release_sample_expr(status_expr);
3247 px->conf.args.ctx = ARGC_SRV;
3248 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003249 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003250 if (!status_expr) {
3251 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3252 goto error;
3253 }
3254 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3255 memprintf(errmsg, "error detected while parsing status-code expression : "
3256 " fetch method '%s' extracts information from '%s', "
3257 "none of which is available here.\n",
3258 args[cur_arg], sample_src_names(status_expr->fetch->use));
3259 goto error;
3260 }
3261 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3262 }
3263 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3264 if (in_pattern) {
3265 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3266 goto error;
3267 }
3268 if (!*(args[cur_arg+1])) {
3269 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3270 goto error;
3271 }
3272 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3273 tout_st = HCHK_STATUS_L7TOUT;
3274 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3275 tout_st = HCHK_STATUS_L6TOUT;
3276 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3277 tout_st = HCHK_STATUS_L4TOUT;
3278 else {
3279 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3280 args[cur_arg], args[cur_arg+1]);
3281 goto error;
3282 }
3283 cur_arg++;
3284 }
3285 else {
3286 if (proto == TCPCHK_RULES_HTTP_CHK) {
3287 bad_http_kw:
3288 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3289 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3290 }
3291 else {
3292 bad_tcp_kw:
3293 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3294 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3295 }
3296 goto error;
3297 }
3298 next:
3299 cur_arg++;
3300 }
3301
3302 chk = calloc(1, sizeof(*chk));
3303 if (!chk) {
3304 memprintf(errmsg, "out of memory");
3305 goto error;
3306 }
3307 chk->action = TCPCHK_ACT_EXPECT;
3308 LIST_INIT(&chk->expect.onerror_fmt);
3309 LIST_INIT(&chk->expect.onsuccess_fmt);
3310 chk->comment = comment; comment = NULL;
3311 chk->expect.type = type;
3312 chk->expect.min_recv = min_recv;
3313 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3314 chk->expect.ok_status = ok_st;
3315 chk->expect.err_status = err_st;
3316 chk->expect.tout_status = tout_st;
3317 chk->expect.status_expr = status_expr; status_expr = NULL;
3318
3319 if (on_success_msg) {
3320 px->conf.args.ctx = ARGC_SRV;
3321 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3322 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3323 goto error;
3324 }
3325 }
3326 if (on_error_msg) {
3327 px->conf.args.ctx = ARGC_SRV;
3328 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3329 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3330 goto error;
3331 }
3332 }
3333
3334 switch (chk->expect.type) {
3335 case TCPCHK_EXPECT_HTTP_STATUS: {
3336 const char *p = pattern;
3337 unsigned int c1,c2;
3338
3339 chk->expect.codes.codes = NULL;
3340 chk->expect.codes.num = 0;
3341 while (1) {
3342 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3343 if (*p == '-') {
3344 p++;
3345 c2 = read_uint(&p, pattern + strlen(pattern));
3346 }
3347 if (c1 > c2) {
3348 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3349 goto error;
3350 }
3351
3352 chk->expect.codes.num++;
3353 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3354 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3355 if (!chk->expect.codes.codes) {
3356 memprintf(errmsg, "out of memory");
3357 goto error;
3358 }
3359 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3360 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3361
3362 if (*p == '\0')
3363 break;
3364 if (*p != ',') {
3365 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3366 goto error;
3367 }
3368 p++;
3369 }
3370 break;
3371 }
3372 case TCPCHK_EXPECT_STRING:
3373 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003374 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003375 if (!isttest(chk->expect.data)) {
3376 memprintf(errmsg, "out of memory");
3377 goto error;
3378 }
3379 break;
3380 case TCPCHK_EXPECT_BINARY: {
3381 int len = chk->expect.data.len;
3382
3383 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3384 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3385 goto error;
3386 }
3387 chk->expect.data.len = len;
3388 break;
3389 }
3390 case TCPCHK_EXPECT_STRING_REGEX:
3391 case TCPCHK_EXPECT_BINARY_REGEX:
3392 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3393 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3394 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3395 if (!chk->expect.regex)
3396 goto error;
3397 break;
3398
3399 case TCPCHK_EXPECT_STRING_LF:
3400 case TCPCHK_EXPECT_BINARY_LF:
3401 case TCPCHK_EXPECT_HTTP_BODY_LF:
3402 LIST_INIT(&chk->expect.fmt);
3403 px->conf.args.ctx = ARGC_SRV;
3404 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3405 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3406 goto error;
3407 }
3408 break;
3409
3410 case TCPCHK_EXPECT_HTTP_HEADER:
3411 if (!npat) {
3412 memprintf(errmsg, "unexpected error, undefined header name pattern");
3413 goto error;
3414 }
3415 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3416 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3417 if (!chk->expect.hdr.name_re)
3418 goto error;
3419 }
3420 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3421 px->conf.args.ctx = ARGC_SRV;
3422 LIST_INIT(&chk->expect.hdr.name_fmt);
3423 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3424 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3425 goto error;
3426 }
3427 }
3428 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003429 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003430 if (!isttest(chk->expect.hdr.name)) {
3431 memprintf(errmsg, "out of memory");
3432 goto error;
3433 }
3434 }
3435
3436 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3437 chk->expect.hdr.value = IST_NULL;
3438 break;
3439 }
3440
3441 if (!vpat) {
3442 memprintf(errmsg, "unexpected error, undefined header value pattern");
3443 goto error;
3444 }
3445 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3446 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3447 if (!chk->expect.hdr.value_re)
3448 goto error;
3449 }
3450 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3451 px->conf.args.ctx = ARGC_SRV;
3452 LIST_INIT(&chk->expect.hdr.value_fmt);
3453 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3454 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3455 goto error;
3456 }
3457 }
3458 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003459 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003460 if (!isttest(chk->expect.hdr.value)) {
3461 memprintf(errmsg, "out of memory");
3462 goto error;
3463 }
3464 }
3465
3466 break;
3467 case TCPCHK_EXPECT_CUSTOM:
3468 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3469 break;
3470 case TCPCHK_EXPECT_UNDEF:
3471 memprintf(errmsg, "pattern not found");
3472 goto error;
3473 }
3474
3475 /* All tcp-check expect points back to the first inverse expect rule in
3476 * a chain of one or more expect rule, potentially itself.
3477 */
3478 chk->expect.head = chk;
3479 list_for_each_entry_rev(prev_check, rules, list) {
3480 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3481 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3482 chk->expect.head = prev_check;
3483 continue;
3484 }
3485 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3486 break;
3487 }
3488 return chk;
3489
3490 error:
3491 free_tcpcheck(chk, 0);
3492 free(comment);
3493 release_sample_expr(status_expr);
3494 return NULL;
3495}
3496
3497/* Overwrites fields of the old http send rule with those of the new one. When
3498 * replaced, old values are freed and replaced by the new ones. New values are
3499 * not copied but transferred. At the end <new> should be empty and can be
3500 * safely released. This function never fails.
3501 */
3502void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3503{
3504 struct logformat_node *lf, *lfb;
3505 struct tcpcheck_http_hdr *hdr, *bhdr;
3506
3507
3508 if (new->send.http.meth.str.area) {
3509 free(old->send.http.meth.str.area);
3510 old->send.http.meth.meth = new->send.http.meth.meth;
3511 old->send.http.meth.str.area = new->send.http.meth.str.area;
3512 old->send.http.meth.str.data = new->send.http.meth.str.data;
3513 new->send.http.meth.str = BUF_NULL;
3514 }
3515
3516 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3517 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3518 istfree(&old->send.http.uri);
3519 else
3520 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3521 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3522 old->send.http.uri = new->send.http.uri;
3523 new->send.http.uri = IST_NULL;
3524 }
3525 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3526 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3527 istfree(&old->send.http.uri);
3528 else
3529 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3530 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3531 LIST_INIT(&old->send.http.uri_fmt);
3532 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003533 LIST_DELETE(&lf->list);
3534 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003535 }
3536 }
3537
3538 if (isttest(new->send.http.vsn)) {
3539 istfree(&old->send.http.vsn);
3540 old->send.http.vsn = new->send.http.vsn;
3541 new->send.http.vsn = IST_NULL;
3542 }
3543
Christopher Faulet4c8e58d2022-07-05 15:33:53 +02003544 if (!LIST_ISEMPTY(&new->send.http.hdrs)) {
3545 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3546 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
3547 LIST_DELETE(&hdr->list);
3548 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
3549 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02003550 }
3551
3552 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3553 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3554 istfree(&old->send.http.body);
3555 else
3556 free_tcpcheck_fmt(&old->send.http.body_fmt);
3557 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3558 old->send.http.body = new->send.http.body;
3559 new->send.http.body = IST_NULL;
3560 }
3561 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3562 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3563 istfree(&old->send.http.body);
3564 else
3565 free_tcpcheck_fmt(&old->send.http.body_fmt);
3566 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3567 LIST_INIT(&old->send.http.body_fmt);
3568 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003569 LIST_DELETE(&lf->list);
3570 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003571 }
3572 }
3573}
3574
3575/* Internal function used to add an http-check rule in a list during the config
3576 * parsing step. Depending on its type, and the previously inserted rules, a
3577 * specific action may be performed or an error may be reported. This functions
3578 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3579 * message.
3580 */
3581int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3582{
3583 struct tcpcheck_rule *r;
3584
3585 /* the implicit send rule coming from an "option httpchk" line must be
3586 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003587 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003588 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003589 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003590 * sure the ruleset remains valid.
3591 */
3592
3593 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3594 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3595 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3596 * following tests are performed :
3597 *
3598 * 1- If there is no such rule or if it is not a send rule, the implicit send
3599 * rule is pushed in front of the ruleset
3600 *
3601 * 2- If it is another implicit send rule, it is replaced with the new one.
3602 *
3603 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3604 * both, overwriting the old send rule (the explicit one) with info of the
3605 * new send rule (the implicit one).
3606 */
3607 r = get_first_tcpcheck_rule(rules);
3608 if (r && r->action == TCPCHK_ACT_CONNECT)
3609 r = get_next_tcpcheck_rule(rules, r);
3610 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003611 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003612 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003613 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003614 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003615 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003616 }
3617 else {
3618 tcpcheck_overwrite_send_http_rule(r, chk);
3619 free_tcpcheck(chk, 0);
3620 }
3621 }
3622 else {
3623 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3624 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3625 * with an existing implicit send rule, if any. At the end, if there is no error,
3626 * the rule is appended to the list.
3627 */
3628
3629 r = get_last_tcpcheck_rule(rules);
3630 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3631 /* no error */;
3632 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3633 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3634 chk->index+1);
3635 return 0;
3636 }
3637 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3638 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3639 chk->index+1);
3640 return 0;
3641 }
3642 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3643 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3644 chk->index+1);
3645 return 0;
3646 }
3647
3648 if (chk->action == TCPCHK_ACT_SEND) {
3649 r = get_first_tcpcheck_rule(rules);
3650 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3651 tcpcheck_overwrite_send_http_rule(r, chk);
3652 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003653 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003654 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3655 chk = r;
3656 }
3657 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003658 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003659 }
3660 return 1;
3661}
3662
3663/* Check tcp-check health-check configuration for the proxy <px>. */
3664static int check_proxy_tcpcheck(struct proxy *px)
3665{
3666 struct tcpcheck_rule *chk, *back;
3667 char *comment = NULL, *errmsg = NULL;
3668 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003669 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003670
3671 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3672 deinit_proxy_tcpcheck(px);
3673 goto out;
3674 }
3675
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003676 ha_free(&px->check_command);
3677 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003678
3679 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003680 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003681 ret |= ERR_ALERT | ERR_FATAL;
3682 goto out;
3683 }
3684
3685 /* HTTP ruleset only : */
3686 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3687 struct tcpcheck_rule *next;
3688
3689 /* move remaining implicit send rule from "option httpchk" line to the right place.
3690 * If such rule exists, it must be the first one. In this case, the rule is moved
3691 * after the first connect rule, if any. Otherwise, nothing is done.
3692 */
3693 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3694 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3695 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3696 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003697 LIST_DELETE(&chk->list);
3698 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003699 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003700 }
3701 }
3702
3703 /* add implicit expect rule if the last one is a send. It is inherited from previous
3704 * versions where the http expect rule was optional. Now it is possible to chained
3705 * send/expect rules but the last expect may still be implicit.
3706 */
3707 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3708 if (chk && chk->action == TCPCHK_ACT_SEND) {
3709 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3710 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3711 px->conf.file, px->conf.line, &errmsg);
3712 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003713 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003714 "(%s).\n", px->id, errmsg);
3715 free(errmsg);
3716 ret |= ERR_ALERT | ERR_FATAL;
3717 goto out;
3718 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003719 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003720 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003721 }
3722 }
3723
3724 /* For all ruleset: */
3725
3726 /* If there is no connect rule preceding all send / expect rules, an
3727 * implicit one is inserted before all others.
3728 */
3729 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3730 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3731 chk = calloc(1, sizeof(*chk));
3732 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003733 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003734 "(out of memory).\n", px->id);
3735 ret |= ERR_ALERT | ERR_FATAL;
3736 goto out;
3737 }
3738 chk->action = TCPCHK_ACT_CONNECT;
3739 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003740 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003741 }
3742
3743 /* Remove all comment rules. To do so, when a such rule is found, the
3744 * comment is assigned to the following rule(s).
3745 */
3746 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Faulet871dd822022-08-24 11:38:03 +02003747 struct tcpcheck_rule *next;
3748
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003749 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3750 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003751
3752 prev_action = chk->action;
3753 switch (chk->action) {
3754 case TCPCHK_ACT_COMMENT:
3755 free(comment);
3756 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003757 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003758 free(chk);
3759 break;
3760 case TCPCHK_ACT_CONNECT:
3761 if (!chk->comment && comment)
3762 chk->comment = strdup(comment);
Christopher Faulet871dd822022-08-24 11:38:03 +02003763 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3764 if (next && next->action == TCPCHK_ACT_SEND)
3765 chk->connect.options |= TCPCHK_OPT_HAS_DATA;
Willy Tarreauf3f60762022-11-14 07:10:33 +01003766 __fallthrough;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003767 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003768 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003769 break;
3770 case TCPCHK_ACT_SEND:
3771 case TCPCHK_ACT_EXPECT:
3772 if (!chk->comment && comment)
3773 chk->comment = strdup(comment);
3774 break;
3775 }
3776 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003777 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003778
3779 out:
3780 return ret;
3781}
3782
3783void deinit_proxy_tcpcheck(struct proxy *px)
3784{
3785 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3786 px->tcpcheck_rules.flags = 0;
3787 px->tcpcheck_rules.list = NULL;
3788}
3789
3790static void deinit_tcpchecks()
3791{
3792 struct tcpcheck_ruleset *rs;
3793 struct tcpcheck_rule *r, *rb;
3794 struct ebpt_node *node, *next;
3795
3796 node = ebpt_first(&shared_tcpchecks);
3797 while (node) {
3798 next = ebpt_next(node);
3799 ebpt_delete(node);
3800 free(node->key);
3801 rs = container_of(node, typeof(*rs), node);
3802 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003803 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003804 free_tcpcheck(r, 0);
3805 }
3806 free(rs);
3807 node = next;
3808 }
3809}
3810
3811int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3812{
3813 struct tcpcheck_rule *tcpcheck, *prev_check;
3814 struct tcpcheck_expect *expect;
3815
Willy Tarreau6922e552021-03-22 21:11:45 +01003816 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003817 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003818 tcpcheck->action = TCPCHK_ACT_EXPECT;
3819
3820 expect = &tcpcheck->expect;
3821 expect->type = TCPCHK_EXPECT_STRING;
3822 LIST_INIT(&expect->onerror_fmt);
3823 LIST_INIT(&expect->onsuccess_fmt);
3824 expect->ok_status = HCHK_STATUS_L7OKD;
3825 expect->err_status = HCHK_STATUS_L7RSP;
3826 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003827 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003828 if (!isttest(expect->data)) {
3829 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3830 return 0;
3831 }
3832
3833 /* All tcp-check expect points back to the first inverse expect rule
3834 * in a chain of one or more expect rule, potentially itself.
3835 */
3836 tcpcheck->expect.head = tcpcheck;
3837 list_for_each_entry_rev(prev_check, rules->list, list) {
3838 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3839 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3840 tcpcheck->expect.head = prev_check;
3841 continue;
3842 }
3843 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3844 break;
3845 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003846 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003847 return 1;
3848}
3849
3850int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3851{
3852 struct tcpcheck_rule *tcpcheck;
3853 struct tcpcheck_send *send;
3854 const char *in;
3855 char *dst;
3856 int i;
3857
Willy Tarreau6922e552021-03-22 21:11:45 +01003858 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003859 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003860 tcpcheck->action = TCPCHK_ACT_SEND;
3861
3862 send = &tcpcheck->send;
3863 send->type = TCPCHK_SEND_STRING;
3864
3865 for (i = 0; strs[i]; i++)
3866 send->data.len += strlen(strs[i]);
3867
3868 send->data.ptr = malloc(istlen(send->data) + 1);
3869 if (!isttest(send->data)) {
3870 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3871 return 0;
3872 }
3873
3874 dst = istptr(send->data);
3875 for (i = 0; strs[i]; i++)
3876 for (in = strs[i]; (*dst = *in++); dst++);
3877 *dst = 0;
3878
Willy Tarreau2b718102021-04-21 07:32:39 +02003879 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003880 return 1;
3881}
3882
3883/* Parses the "tcp-check" proxy keyword */
3884static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003885 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003886 char **errmsg)
3887{
3888 struct tcpcheck_ruleset *rs = NULL;
3889 struct tcpcheck_rule *chk = NULL;
3890 int index, cur_arg, ret = 0;
3891
3892 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3893 ret = 1;
3894
3895 /* Deduce the ruleset name from the proxy info */
3896 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3897 ((curpx == defpx) ? "defaults" : curpx->id),
3898 curpx->conf.file, curpx->conf.line);
3899
3900 rs = find_tcpcheck_ruleset(b_orig(&trash));
3901 if (rs == NULL) {
3902 rs = create_tcpcheck_ruleset(b_orig(&trash));
3903 if (rs == NULL) {
3904 memprintf(errmsg, "out of memory.\n");
3905 goto error;
3906 }
3907 }
3908
3909 index = 0;
3910 if (!LIST_ISEMPTY(&rs->rules)) {
3911 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3912 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003913 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003914 }
3915
3916 cur_arg = 1;
3917 if (strcmp(args[cur_arg], "connect") == 0)
3918 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3919 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3920 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3921 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3922 else if (strcmp(args[cur_arg], "expect") == 0)
3923 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3924 else if (strcmp(args[cur_arg], "comment") == 0)
3925 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3926 else {
3927 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3928
3929 if (!kw) {
3930 action_kw_tcp_check_build_list(&trash);
3931 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3932 "%s%s. but got '%s'",
3933 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3934 goto error;
3935 }
3936 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3937 }
3938
3939 if (!chk) {
3940 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3941 goto error;
3942 }
3943 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3944
3945 /* No error: add the tcp-check rule in the list */
3946 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003947 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003948
3949 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3950 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3951 /* Use this ruleset if the proxy already has tcp-check enabled */
3952 curpx->tcpcheck_rules.list = &rs->rules;
3953 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3954 }
3955 else {
3956 /* mark this ruleset as unused for now */
3957 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3958 }
3959
3960 return ret;
3961
3962 error:
3963 free_tcpcheck(chk, 0);
3964 free_tcpcheck_ruleset(rs);
3965 return -1;
3966}
3967
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003968/* Parses the "http-check" proxy keyword */
3969static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003970 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003971 char **errmsg)
3972{
3973 struct tcpcheck_ruleset *rs = NULL;
3974 struct tcpcheck_rule *chk = NULL;
3975 int index, cur_arg, ret = 0;
3976
3977 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3978 ret = 1;
3979
3980 cur_arg = 1;
3981 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3982 /* enable a graceful server shutdown on an HTTP 404 response */
3983 curpx->options |= PR_O_DISABLE404;
3984 if (too_many_args(1, args, errmsg, NULL))
3985 goto error;
3986 goto out;
3987 }
3988 else if (strcmp(args[cur_arg], "send-state") == 0) {
3989 /* enable emission of the apparent state of a server in HTTP checks */
3990 curpx->options2 |= PR_O2_CHK_SNDST;
3991 if (too_many_args(1, args, errmsg, NULL))
3992 goto error;
3993 goto out;
3994 }
3995
3996 /* Deduce the ruleset name from the proxy info */
3997 chunk_printf(&trash, "*http-check-%s_%s-%d",
3998 ((curpx == defpx) ? "defaults" : curpx->id),
3999 curpx->conf.file, curpx->conf.line);
4000
4001 rs = find_tcpcheck_ruleset(b_orig(&trash));
4002 if (rs == NULL) {
4003 rs = create_tcpcheck_ruleset(b_orig(&trash));
4004 if (rs == NULL) {
4005 memprintf(errmsg, "out of memory.\n");
4006 goto error;
4007 }
4008 }
4009
4010 index = 0;
4011 if (!LIST_ISEMPTY(&rs->rules)) {
4012 chk = LIST_PREV(&rs->rules, typeof(chk), list);
4013 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4014 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004015 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004016 }
4017
4018 if (strcmp(args[cur_arg], "connect") == 0)
4019 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4020 else if (strcmp(args[cur_arg], "send") == 0)
4021 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4022 else if (strcmp(args[cur_arg], "expect") == 0)
4023 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4024 file, line, errmsg);
4025 else if (strcmp(args[cur_arg], "comment") == 0)
4026 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4027 else {
4028 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4029
4030 if (!kw) {
4031 action_kw_tcp_check_build_list(&trash);
4032 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4033 " 'send', 'expect'%s%s. but got '%s'",
4034 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4035 goto error;
4036 }
4037 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4038 }
4039
4040 if (!chk) {
4041 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4042 goto error;
4043 }
4044 ret = (*errmsg != NULL); /* Handle warning */
4045
4046 chk->index = index;
4047 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4048 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4049 /* Use this ruleset if the proxy already has http-check enabled */
4050 curpx->tcpcheck_rules.list = &rs->rules;
4051 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4052 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4053 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4054 curpx->tcpcheck_rules.list = NULL;
4055 goto error;
4056 }
4057 }
4058 else {
4059 /* mark this ruleset as unused for now */
4060 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004061 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004062 }
4063
4064 out:
4065 return ret;
4066
4067 error:
4068 free_tcpcheck(chk, 0);
4069 free_tcpcheck_ruleset(rs);
4070 return -1;
4071}
4072
4073/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004074int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004075 const char *file, int line)
4076{
4077 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4078 static char *redis_res = "+PONG\r\n";
4079
4080 struct tcpcheck_ruleset *rs = NULL;
4081 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4082 struct tcpcheck_rule *chk;
4083 char *errmsg = NULL;
4084 int err_code = 0;
4085
4086 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4087 err_code |= ERR_WARN;
4088
4089 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4090 goto out;
4091
4092 curpx->options2 &= ~PR_O2_CHK_ANY;
4093 curpx->options2 |= PR_O2_TCPCHK_CHK;
4094
4095 free_tcpcheck_vars(&rules->preset_vars);
4096 rules->list = NULL;
4097 rules->flags = 0;
4098
4099 rs = find_tcpcheck_ruleset("*redis-check");
4100 if (rs)
4101 goto ruleset_found;
4102
4103 rs = create_tcpcheck_ruleset("*redis-check");
4104 if (rs == NULL) {
4105 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4106 goto error;
4107 }
4108
4109 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4110 1, curpx, &rs->rules, file, line, &errmsg);
4111 if (!chk) {
4112 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4113 goto error;
4114 }
4115 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004116 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004117
4118 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4119 "error-status", "L7STS",
4120 "on-error", "%[res.payload(0,0),cut_crlf]",
4121 "on-success", "Redis server is ok",
4122 ""},
4123 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4124 if (!chk) {
4125 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4126 goto error;
4127 }
4128 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004129 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004130
4131 ruleset_found:
4132 rules->list = &rs->rules;
4133 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4134 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4135
4136 out:
4137 free(errmsg);
4138 return err_code;
4139
4140 error:
4141 free_tcpcheck_ruleset(rs);
4142 err_code |= ERR_ALERT | ERR_FATAL;
4143 goto out;
4144}
4145
4146
4147/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004148int 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 +01004149 const char *file, int line)
4150{
4151 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4152 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4153 *
4154 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4155 */
4156 static char sslv3_client_hello[] = {
4157 "16" /* ContentType : 0x16 = Handshake */
4158 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4159 "0079" /* ContentLength : 0x79 bytes after this one */
4160 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4161 "000075" /* HandshakeLength : 0x75 bytes after this one */
4162 "0300" /* Hello Version : 0x0300 = v3 */
4163 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4164 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4165 "00" /* Session ID length : empty (no session ID) */
4166 "004E" /* Cipher Suite Length : 78 bytes after this one */
4167 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4168 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4169 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4170 "000D" "000E" "000F" "0010" /* various bit lengths, */
4171 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4172 "0015" "0016" "0017" "0018"
4173 "0019" "001A" "001B" "002F"
4174 "0030" "0031" "0032" "0033"
4175 "0034" "0035" "0036" "0037"
4176 "0038" "0039" "003A"
4177 "01" /* Compression Length : 0x01 = 1 byte for types */
4178 "00" /* Compression Type : 0x00 = NULL compression */
4179 };
4180
4181 struct tcpcheck_ruleset *rs = NULL;
4182 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4183 struct tcpcheck_rule *chk;
4184 char *errmsg = NULL;
4185 int err_code = 0;
4186
4187 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4188 err_code |= ERR_WARN;
4189
4190 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4191 goto out;
4192
4193 curpx->options2 &= ~PR_O2_CHK_ANY;
4194 curpx->options2 |= PR_O2_TCPCHK_CHK;
4195
4196 free_tcpcheck_vars(&rules->preset_vars);
4197 rules->list = NULL;
4198 rules->flags = 0;
4199
4200 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4201 if (rs)
4202 goto ruleset_found;
4203
4204 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4205 if (rs == NULL) {
4206 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4207 goto error;
4208 }
4209
4210 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4211 1, curpx, &rs->rules, file, line, &errmsg);
4212 if (!chk) {
4213 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4214 goto error;
4215 }
4216 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004217 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004218
4219 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4220 "min-recv", "5", "ok-status", "L6OK",
4221 "error-status", "L6RSP", "tout-status", "L6TOUT",
4222 ""},
4223 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4224 if (!chk) {
4225 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4226 goto error;
4227 }
4228 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004229 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004230
4231 ruleset_found:
4232 rules->list = &rs->rules;
4233 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4234 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4235
4236 out:
4237 free(errmsg);
4238 return err_code;
4239
4240 error:
4241 free_tcpcheck_ruleset(rs);
4242 err_code |= ERR_ALERT | ERR_FATAL;
4243 goto out;
4244}
4245
4246/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004247int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004248 const char *file, int line)
4249{
4250 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4251
4252 struct tcpcheck_ruleset *rs = NULL;
4253 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4254 struct tcpcheck_rule *chk;
4255 struct tcpcheck_var *var = NULL;
4256 char *cmd = NULL, *errmsg = NULL;
4257 int err_code = 0;
4258
4259 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4260 err_code |= ERR_WARN;
4261
4262 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4263 goto out;
4264
4265 curpx->options2 &= ~PR_O2_CHK_ANY;
4266 curpx->options2 |= PR_O2_TCPCHK_CHK;
4267
4268 free_tcpcheck_vars(&rules->preset_vars);
4269 rules->list = NULL;
4270 rules->flags = 0;
4271
4272 cur_arg += 2;
4273 if (*args[cur_arg] && *args[cur_arg+1] &&
4274 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4275 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4276 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4277 if (cmd)
4278 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4279 }
4280 else {
4281 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4282 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4283 cmd = strdup("HELO localhost");
4284 }
4285
4286 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4287 if (cmd == NULL || var == NULL) {
4288 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4289 goto error;
4290 }
4291 var->data.type = SMP_T_STR;
4292 var->data.u.str.area = cmd;
4293 var->data.u.str.data = strlen(cmd);
4294 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004295 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004296 cmd = NULL;
4297 var = NULL;
4298
4299 rs = find_tcpcheck_ruleset("*smtp-check");
4300 if (rs)
4301 goto ruleset_found;
4302
4303 rs = create_tcpcheck_ruleset("*smtp-check");
4304 if (rs == NULL) {
4305 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4306 goto error;
4307 }
4308
4309 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4310 1, curpx, &rs->rules, file, line, &errmsg);
4311 if (!chk) {
4312 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4313 goto error;
4314 }
4315 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004316 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004317
4318 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4319 "min-recv", "4",
4320 "error-status", "L7RSP",
4321 "on-error", "%[res.payload(0,0),cut_crlf]",
4322 ""},
4323 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4324 if (!chk) {
4325 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4326 goto error;
4327 }
4328 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004329 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004330
4331 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4332 "min-recv", "4",
4333 "error-status", "L7STS",
4334 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4335 "status-code", "res.payload(0,3)",
4336 ""},
4337 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4338 if (!chk) {
4339 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4340 goto error;
4341 }
4342 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004343 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004344
4345 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4346 1, curpx, &rs->rules, file, line, &errmsg);
4347 if (!chk) {
4348 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4349 goto error;
4350 }
4351 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004352 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004353
Christopher Faulet2ec1ffa2022-09-21 14:42:47 +02004354 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 +01004355 "error-status", "L7STS",
4356 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4357 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4358 "status-code", "res.payload(0,3)",
4359 ""},
4360 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4361 if (!chk) {
4362 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4363 goto error;
4364 }
4365 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004366 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004367
wrightlaw9a8d8a32022-09-08 16:10:48 +01004368 /* Send an SMTP QUIT to ensure clean disconnect (issue 1812), and expect a 2xx response code */
4369
4370 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "QUIT\r\n", ""},
4371 1, curpx, &rs->rules, file, line, &errmsg);
4372 if (!chk) {
4373 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4374 goto error;
4375 }
4376 chk->index = 5;
4377 LIST_APPEND(&rs->rules, &chk->list);
4378
4379 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4380 "min-recv", "4",
4381 "error-status", "L7STS",
4382 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4383 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4384 "status-code", "res.payload(0,3)",
4385 ""},
4386 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4387 if (!chk) {
4388 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4389 goto error;
4390 }
4391 chk->index = 6;
4392 LIST_APPEND(&rs->rules, &chk->list);
4393
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004394 ruleset_found:
4395 rules->list = &rs->rules;
4396 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4397 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4398
4399 out:
4400 free(errmsg);
4401 return err_code;
4402
4403 error:
4404 free(cmd);
4405 free(var);
4406 free_tcpcheck_vars(&rules->preset_vars);
4407 free_tcpcheck_ruleset(rs);
4408 err_code |= ERR_ALERT | ERR_FATAL;
4409 goto out;
4410}
4411
4412/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004413int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004414 const char *file, int line)
4415{
4416 static char pgsql_req[] = {
4417 "%[var(check.plen),htonl,hex]" /* The packet length*/
4418 "00030000" /* the version 3.0 */
4419 "7573657200" /* "user" key */
4420 "%[var(check.username),hex]00" /* the username */
4421 "00"
4422 };
4423
4424 struct tcpcheck_ruleset *rs = NULL;
4425 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4426 struct tcpcheck_rule *chk;
4427 struct tcpcheck_var *var = NULL;
4428 char *user = NULL, *errmsg = NULL;
4429 size_t packetlen = 0;
4430 int err_code = 0;
4431
4432 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4433 err_code |= ERR_WARN;
4434
4435 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4436 goto out;
4437
4438 curpx->options2 &= ~PR_O2_CHK_ANY;
4439 curpx->options2 |= PR_O2_TCPCHK_CHK;
4440
4441 free_tcpcheck_vars(&rules->preset_vars);
4442 rules->list = NULL;
4443 rules->flags = 0;
4444
4445 cur_arg += 2;
4446 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4447 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4448 file, line, args[0], args[1]);
4449 goto error;
4450 }
4451 if (strcmp(args[cur_arg], "user") == 0) {
4452 packetlen = 15 + strlen(args[cur_arg+1]);
4453 user = strdup(args[cur_arg+1]);
4454
4455 var = create_tcpcheck_var(ist("check.username"));
4456 if (user == NULL || var == NULL) {
4457 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4458 goto error;
4459 }
4460 var->data.type = SMP_T_STR;
4461 var->data.u.str.area = user;
4462 var->data.u.str.data = strlen(user);
4463 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004464 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004465 user = NULL;
4466 var = NULL;
4467
4468 var = create_tcpcheck_var(ist("check.plen"));
4469 if (var == NULL) {
4470 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4471 goto error;
4472 }
4473 var->data.type = SMP_T_SINT;
4474 var->data.u.sint = packetlen;
4475 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004476 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004477 var = NULL;
4478 }
4479 else {
4480 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4481 file, line, args[0], args[1]);
4482 goto error;
4483 }
4484
4485 rs = find_tcpcheck_ruleset("*pgsql-check");
4486 if (rs)
4487 goto ruleset_found;
4488
4489 rs = create_tcpcheck_ruleset("*pgsql-check");
4490 if (rs == NULL) {
4491 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4492 goto error;
4493 }
4494
4495 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4496 1, curpx, &rs->rules, file, line, &errmsg);
4497 if (!chk) {
4498 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4499 goto error;
4500 }
4501 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004502 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004503
4504 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4505 1, curpx, &rs->rules, file, line, &errmsg);
4506 if (!chk) {
4507 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4508 goto error;
4509 }
4510 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004511 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004512
4513 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4514 "min-recv", "5",
4515 "error-status", "L7RSP",
4516 "on-error", "%[res.payload(6,0)]",
4517 ""},
4518 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4519 if (!chk) {
4520 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4521 goto error;
4522 }
4523 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004524 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004525
Fatih Acar0d6fb7a2022-09-26 17:27:11 +02004526 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 +01004527 "min-recv", "9",
4528 "error-status", "L7STS",
4529 "on-success", "PostgreSQL server is ok",
4530 "on-error", "PostgreSQL unknown error",
4531 ""},
4532 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4533 if (!chk) {
4534 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4535 goto error;
4536 }
4537 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004538 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004539
4540 ruleset_found:
4541 rules->list = &rs->rules;
4542 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4543 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4544
4545 out:
4546 free(errmsg);
4547 return err_code;
4548
4549 error:
4550 free(user);
4551 free(var);
4552 free_tcpcheck_vars(&rules->preset_vars);
4553 free_tcpcheck_ruleset(rs);
4554 err_code |= ERR_ALERT | ERR_FATAL;
4555 goto out;
4556}
4557
4558
4559/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004560int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004561 const char *file, int line)
4562{
4563 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4564 * const char mysql40_client_auth_pkt[] = {
4565 * "\x0e\x00\x00" // packet length
4566 * "\x01" // packet number
4567 * "\x00\x00" // client capabilities
4568 * "\x00\x00\x01" // max packet
4569 * "haproxy\x00" // username (null terminated string)
4570 * "\x00" // filler (always 0x00)
4571 * "\x01\x00\x00" // packet length
4572 * "\x00" // packet number
4573 * "\x01" // COM_QUIT command
4574 * };
4575 */
4576 static char mysql40_rsname[] = "*mysql40-check";
4577 static char mysql40_req[] = {
4578 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4579 "0080" /* client capabilities */
4580 "000001" /* max packet */
4581 "%[var(check.username),hex]00" /* the username */
4582 "00" /* filler (always 0x00) */
4583 "010000" /* packet length*/
4584 "00" /* sequence ID */
4585 "01" /* COM_QUIT command */
4586 };
4587
4588 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4589 * const char mysql41_client_auth_pkt[] = {
4590 * "\x0e\x00\x00\" // packet length
4591 * "\x01" // packet number
4592 * "\x00\x00\x00\x00" // client capabilities
4593 * "\x00\x00\x00\x01" // max packet
4594 * "\x21" // character set (UTF-8)
4595 * char[23] // All zeroes
4596 * "haproxy\x00" // username (null terminated string)
4597 * "\x00" // filler (always 0x00)
4598 * "\x01\x00\x00" // packet length
4599 * "\x00" // packet number
4600 * "\x01" // COM_QUIT command
4601 * };
4602 */
4603 static char mysql41_rsname[] = "*mysql41-check";
4604 static char mysql41_req[] = {
4605 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4606 "00820000" /* client capabilities */
4607 "00800001" /* max packet */
4608 "21" /* character set (UTF-8) */
4609 "000000000000000000000000" /* 23 bytes, al zeroes */
4610 "0000000000000000000000"
4611 "%[var(check.username),hex]00" /* the username */
4612 "00" /* filler (always 0x00) */
4613 "010000" /* packet length*/
4614 "00" /* sequence ID */
4615 "01" /* COM_QUIT command */
4616 };
4617
4618 struct tcpcheck_ruleset *rs = NULL;
4619 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4620 struct tcpcheck_rule *chk;
4621 struct tcpcheck_var *var = NULL;
4622 char *mysql_rsname = "*mysql-check";
4623 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4624 int index = 0, err_code = 0;
4625
4626 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4627 err_code |= ERR_WARN;
4628
4629 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4630 goto out;
4631
4632 curpx->options2 &= ~PR_O2_CHK_ANY;
4633 curpx->options2 |= PR_O2_TCPCHK_CHK;
4634
4635 free_tcpcheck_vars(&rules->preset_vars);
4636 rules->list = NULL;
4637 rules->flags = 0;
4638
4639 cur_arg += 2;
4640 if (*args[cur_arg]) {
4641 int packetlen, userlen;
4642
4643 if (strcmp(args[cur_arg], "user") != 0) {
4644 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4645 file, line, args[0], args[1], args[cur_arg]);
4646 goto error;
4647 }
4648
4649 if (*(args[cur_arg+1]) == 0) {
4650 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4651 file, line, args[0], args[1], args[cur_arg]);
4652 goto error;
4653 }
4654
4655 hdr = calloc(4, sizeof(*hdr));
4656 user = strdup(args[cur_arg+1]);
4657 userlen = strlen(args[cur_arg+1]);
4658
4659 if (hdr == NULL || user == NULL) {
4660 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4661 goto error;
4662 }
4663
4664 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4665 packetlen = userlen + 7 + 27;
4666 mysql_req = mysql41_req;
4667 mysql_rsname = mysql41_rsname;
4668 }
4669 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4670 packetlen = userlen + 7;
4671 mysql_req = mysql40_req;
4672 mysql_rsname = mysql40_rsname;
4673 }
4674 else {
4675 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4676 file, line, args[cur_arg], args[cur_arg+2]);
4677 goto error;
4678 }
4679
4680 hdr[0] = (unsigned char)(packetlen & 0xff);
4681 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4682 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4683 hdr[3] = 1;
4684
4685 var = create_tcpcheck_var(ist("check.header"));
4686 if (var == NULL) {
4687 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4688 goto error;
4689 }
4690 var->data.type = SMP_T_STR;
4691 var->data.u.str.area = hdr;
4692 var->data.u.str.data = 4;
4693 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004694 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004695 hdr = NULL;
4696 var = NULL;
4697
4698 var = create_tcpcheck_var(ist("check.username"));
4699 if (var == NULL) {
4700 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4701 goto error;
4702 }
4703 var->data.type = SMP_T_STR;
4704 var->data.u.str.area = user;
4705 var->data.u.str.data = strlen(user);
4706 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004707 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004708 user = NULL;
4709 var = NULL;
4710 }
4711
4712 rs = find_tcpcheck_ruleset(mysql_rsname);
4713 if (rs)
4714 goto ruleset_found;
4715
4716 rs = create_tcpcheck_ruleset(mysql_rsname);
4717 if (rs == NULL) {
4718 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4719 goto error;
4720 }
4721
4722 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4723 1, curpx, &rs->rules, file, line, &errmsg);
4724 if (!chk) {
4725 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4726 goto error;
4727 }
4728 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004729 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004730
4731 if (mysql_req) {
4732 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4733 1, curpx, &rs->rules, file, line, &errmsg);
4734 if (!chk) {
4735 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4736 goto error;
4737 }
4738 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004739 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004740 }
4741
4742 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4743 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4744 if (!chk) {
4745 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4746 goto error;
4747 }
4748 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4749 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004750 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004751
4752 if (mysql_req) {
4753 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4754 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4755 if (!chk) {
4756 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4757 goto error;
4758 }
4759 chk->expect.custom = tcpcheck_mysql_expect_ok;
4760 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004761 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004762 }
4763
4764 ruleset_found:
4765 rules->list = &rs->rules;
4766 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4767 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4768
4769 out:
4770 free(errmsg);
4771 return err_code;
4772
4773 error:
4774 free(hdr);
4775 free(user);
4776 free(var);
4777 free_tcpcheck_vars(&rules->preset_vars);
4778 free_tcpcheck_ruleset(rs);
4779 err_code |= ERR_ALERT | ERR_FATAL;
4780 goto out;
4781}
4782
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004783int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004784 const char *file, int line)
4785{
4786 static char *ldap_req = "300C020101600702010304008000";
4787
4788 struct tcpcheck_ruleset *rs = NULL;
4789 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4790 struct tcpcheck_rule *chk;
4791 char *errmsg = NULL;
4792 int err_code = 0;
4793
4794 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4795 err_code |= ERR_WARN;
4796
4797 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4798 goto out;
4799
4800 curpx->options2 &= ~PR_O2_CHK_ANY;
4801 curpx->options2 |= PR_O2_TCPCHK_CHK;
4802
4803 free_tcpcheck_vars(&rules->preset_vars);
4804 rules->list = NULL;
4805 rules->flags = 0;
4806
4807 rs = find_tcpcheck_ruleset("*ldap-check");
4808 if (rs)
4809 goto ruleset_found;
4810
4811 rs = create_tcpcheck_ruleset("*ldap-check");
4812 if (rs == NULL) {
4813 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4814 goto error;
4815 }
4816
4817 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4818 1, curpx, &rs->rules, file, line, &errmsg);
4819 if (!chk) {
4820 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4821 goto error;
4822 }
4823 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004824 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004825
4826 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4827 "min-recv", "14",
4828 "on-error", "Not LDAPv3 protocol",
4829 ""},
4830 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4831 if (!chk) {
4832 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4833 goto error;
4834 }
4835 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004836 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004837
4838 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4839 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4840 if (!chk) {
4841 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4842 goto error;
4843 }
4844 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4845 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004846 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004847
4848 ruleset_found:
4849 rules->list = &rs->rules;
4850 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4851 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4852
4853 out:
4854 free(errmsg);
4855 return err_code;
4856
4857 error:
4858 free_tcpcheck_ruleset(rs);
4859 err_code |= ERR_ALERT | ERR_FATAL;
4860 goto out;
4861}
4862
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004863int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004864 const char *file, int line)
4865{
4866 struct tcpcheck_ruleset *rs = NULL;
4867 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4868 struct tcpcheck_rule *chk;
4869 char *spop_req = NULL;
4870 char *errmsg = NULL;
4871 int spop_len = 0, err_code = 0;
4872
4873 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4874 err_code |= ERR_WARN;
4875
4876 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4877 goto out;
4878
4879 curpx->options2 &= ~PR_O2_CHK_ANY;
4880 curpx->options2 |= PR_O2_TCPCHK_CHK;
4881
4882 free_tcpcheck_vars(&rules->preset_vars);
4883 rules->list = NULL;
4884 rules->flags = 0;
4885
4886
4887 rs = find_tcpcheck_ruleset("*spop-check");
4888 if (rs)
4889 goto ruleset_found;
4890
4891 rs = create_tcpcheck_ruleset("*spop-check");
4892 if (rs == NULL) {
4893 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4894 goto error;
4895 }
4896
4897 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4898 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4899 goto error;
4900 }
4901 chunk_reset(&trash);
4902 dump_binary(&trash, spop_req, spop_len);
4903 trash.area[trash.data] = '\0';
4904
4905 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4906 1, curpx, &rs->rules, file, line, &errmsg);
4907 if (!chk) {
4908 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4909 goto error;
4910 }
4911 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004912 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004913
4914 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4915 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4916 if (!chk) {
4917 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4918 goto error;
4919 }
4920 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4921 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004922 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004923
4924 ruleset_found:
4925 rules->list = &rs->rules;
4926 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4927 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4928
4929 out:
4930 free(spop_req);
4931 free(errmsg);
4932 return err_code;
4933
4934 error:
4935 free_tcpcheck_ruleset(rs);
4936 err_code |= ERR_ALERT | ERR_FATAL;
4937 goto out;
4938}
4939
4940
4941static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4942{
4943 struct tcpcheck_rule *chk = NULL;
4944 struct tcpcheck_http_hdr *hdr = NULL;
4945 char *meth = NULL, *uri = NULL, *vsn = NULL;
4946 char *hdrs, *body;
4947
4948 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4949 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004950 if (hdrs || body) {
Christopher Faulet4b5f3022022-09-05 09:05:17 +02004951 memprintf(errmsg, "hiding headers or body at the end of the version string is unsupported."
4952 "Use 'http-check send' directive instead.");
4953 goto error;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004954 }
4955
4956 chk = calloc(1, sizeof(*chk));
4957 if (!chk) {
4958 memprintf(errmsg, "out of memory");
4959 goto error;
4960 }
4961 chk->action = TCPCHK_ACT_SEND;
4962 chk->send.type = TCPCHK_SEND_HTTP;
4963 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4964 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4965 LIST_INIT(&chk->send.http.hdrs);
4966
4967 /* Copy the method, uri and version */
4968 if (*args[cur_arg]) {
4969 if (!*args[cur_arg+1])
4970 uri = args[cur_arg];
4971 else
4972 meth = args[cur_arg];
4973 }
4974 if (*args[cur_arg+1])
4975 uri = args[cur_arg+1];
4976 if (*args[cur_arg+2])
4977 vsn = args[cur_arg+2];
4978
4979 if (meth) {
4980 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4981 chk->send.http.meth.str.area = strdup(meth);
4982 chk->send.http.meth.str.data = strlen(meth);
4983 if (!chk->send.http.meth.str.area) {
4984 memprintf(errmsg, "out of memory");
4985 goto error;
4986 }
4987 }
4988 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004989 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004990 if (!isttest(chk->send.http.uri)) {
4991 memprintf(errmsg, "out of memory");
4992 goto error;
4993 }
4994 }
4995 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004996 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004997 if (!isttest(chk->send.http.vsn)) {
4998 memprintf(errmsg, "out of memory");
4999 goto error;
5000 }
5001 }
5002
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005003 return chk;
5004
5005 error:
5006 free_tcpcheck_http_hdr(hdr);
5007 free_tcpcheck(chk, 0);
5008 return NULL;
5009}
5010
5011/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005012int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005013 const char *file, int line)
5014{
5015 struct tcpcheck_ruleset *rs = NULL;
5016 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5017 struct tcpcheck_rule *chk;
5018 char *errmsg = NULL;
5019 int err_code = 0;
5020
5021 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5022 err_code |= ERR_WARN;
5023
5024 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5025 goto out;
5026
5027 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5028 if (!chk) {
5029 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5030 goto error;
5031 }
5032 if (errmsg) {
5033 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5034 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005035 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005036 }
5037
5038 no_request:
5039 curpx->options2 &= ~PR_O2_CHK_ANY;
5040 curpx->options2 |= PR_O2_TCPCHK_CHK;
5041
5042 free_tcpcheck_vars(&rules->preset_vars);
5043 rules->list = NULL;
5044 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5045
5046 /* Deduce the ruleset name from the proxy info */
5047 chunk_printf(&trash, "*http-check-%s_%s-%d",
5048 ((curpx == defpx) ? "defaults" : curpx->id),
5049 curpx->conf.file, curpx->conf.line);
5050
5051 rs = find_tcpcheck_ruleset(b_orig(&trash));
5052 if (rs == NULL) {
5053 rs = create_tcpcheck_ruleset(b_orig(&trash));
5054 if (rs == NULL) {
5055 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5056 goto error;
5057 }
5058 }
5059
5060 rules->list = &rs->rules;
5061 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5062 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5063 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5064 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5065 rules->list = NULL;
5066 goto error;
5067 }
5068
5069 out:
5070 free(errmsg);
5071 return err_code;
5072
5073 error:
5074 free_tcpcheck_ruleset(rs);
5075 free_tcpcheck(chk, 0);
5076 err_code |= ERR_ALERT | ERR_FATAL;
5077 goto out;
5078}
5079
5080/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005081int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005082 const char *file, int line)
5083{
5084 struct tcpcheck_ruleset *rs = NULL;
5085 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5086 int err_code = 0;
5087
5088 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5089 err_code |= ERR_WARN;
5090
5091 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5092 goto out;
5093
5094 curpx->options2 &= ~PR_O2_CHK_ANY;
5095 curpx->options2 |= PR_O2_TCPCHK_CHK;
5096
5097 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5098 /* If a tcp-check rulesset is already set, do nothing */
5099 if (rules->list)
5100 goto out;
5101
5102 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5103 * get it.
5104 */
5105 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5106 goto curpx_ruleset;
5107
5108 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5109 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5110 rs = find_tcpcheck_ruleset(b_orig(&trash));
5111 if (rs)
5112 goto ruleset_found;
5113 }
5114
5115 curpx_ruleset:
5116 /* Deduce the ruleset name from the proxy info */
5117 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5118 ((curpx == defpx) ? "defaults" : curpx->id),
5119 curpx->conf.file, curpx->conf.line);
5120
5121 rs = find_tcpcheck_ruleset(b_orig(&trash));
5122 if (rs == NULL) {
5123 rs = create_tcpcheck_ruleset(b_orig(&trash));
5124 if (rs == NULL) {
5125 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5126 goto error;
5127 }
5128 }
5129
5130 ruleset_found:
5131 free_tcpcheck_vars(&rules->preset_vars);
5132 rules->list = &rs->rules;
5133 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5134 rules->flags |= TCPCHK_RULES_TCP_CHK;
5135
5136 out:
5137 return err_code;
5138
5139 error:
5140 err_code |= ERR_ALERT | ERR_FATAL;
5141 goto out;
5142}
5143
Willy Tarreau51cd5952020-06-05 12:25:38 +02005144static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005145 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005146 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5147 { 0, NULL, NULL },
5148}};
5149
5150REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5151REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5152REGISTER_POST_DEINIT(deinit_tcpchecks);
5153INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);