blob: bd775a21be676e16b62f9e10bff3ea36858a162c [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;
Christopher Fauleta664aa62023-03-20 16:49:51 +0100424 int is_empty;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200425
426 /* Follows these step to produce the info message:
427 * 1. if info field is already provided, copy it
428 * 2. if the expect rule provides an onerror log-format string,
429 * use it to produce the message
430 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
431 * 4. Otherwise produce the generic tcp-check info message
432 */
433 if (istlen(info)) {
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100434 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200435 goto comment;
436 }
437 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
438 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
439 goto comment;
440 }
441
Christopher Fauleta664aa62023-03-20 16:49:51 +0100442 is_empty = (IS_HTX_SC(check->sc) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
443 if (is_empty) {
444 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
445 chunk_printf(msg, "TCPCHK got an empty response at step %d",
446 tcpcheck_get_step_id(check, rule));
447 goto comment;
448 }
449
450 if (check->type == PR_O2_TCPCHK_CHK &&
451 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK) {
452 goto comment;
453 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200454
455 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
456 switch (rule->expect.type) {
457 case TCPCHK_EXPECT_HTTP_STATUS:
458 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
459 break;
460 case TCPCHK_EXPECT_STRING:
461 case TCPCHK_EXPECT_HTTP_BODY:
462 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
463 tcpcheck_get_step_id(check, rule));
464 break;
465 case TCPCHK_EXPECT_BINARY:
466 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
467 break;
468 case TCPCHK_EXPECT_STRING_REGEX:
469 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
470 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
471 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
472 break;
473 case TCPCHK_EXPECT_BINARY_REGEX:
474 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
475 break;
476 case TCPCHK_EXPECT_STRING_LF:
477 case TCPCHK_EXPECT_HTTP_BODY_LF:
478 chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule));
479 break;
480 case TCPCHK_EXPECT_BINARY_LF:
481 chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule));
482 break;
483 case TCPCHK_EXPECT_CUSTOM:
484 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
485 break;
486 case TCPCHK_EXPECT_HTTP_HEADER:
487 chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
488 case TCPCHK_EXPECT_UNDEF:
489 /* Should never happen. */
490 return;
491 }
492
493 comment:
494 /* If the failing expect rule provides a comment, it is concatenated to
495 * the info message.
496 */
497 if (rule->comment) {
498 chunk_strcat(msg, " comment: ");
499 chunk_strcat(msg, rule->comment);
500 }
501
502 /* Finally, the check status code is set if the failing expect rule
503 * defines a status expression.
504 */
505 if (rule->expect.status_expr) {
506 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
507 rule->expect.status_expr, SMP_T_STR);
508
509 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
510 sample_casts[smp->data.type][SMP_T_SINT](smp))
511 check->code = smp->data.u.sint;
512 }
513
514 *(b_tail(msg)) = '\0';
515}
516
517/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
518static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
519 struct ist info)
520{
521 struct sample *smp;
522
523 /* Follows these step to produce the info message:
524 * 1. if info field is already provided, copy it
525 * 2. if the expect rule provides an onsucces log-format string,
526 * use it to produce the message
527 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
528 * 4. Otherwise produce the generic tcp-check info message
529 */
530 if (istlen(info))
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100531 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200532 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
533 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
534 &rule->expect.onsuccess_fmt);
535 else if (check->type == PR_O2_TCPCHK_CHK &&
536 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
537 chunk_strcat(msg, "(tcp-check)");
538
539 /* Finally, the check status code is set if the expect rule defines a
540 * status expression.
541 */
542 if (rule->expect.status_expr) {
543 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
544 rule->expect.status_expr, SMP_T_STR);
545
546 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
547 sample_casts[smp->data.type][SMP_T_SINT](smp))
548 check->code = smp->data.u.sint;
549 }
550
551 *(b_tail(msg)) = '\0';
552}
553
554/* Internal functions to parse and validate a MySQL packet in the context of an
555 * expect rule. It start to parse the input buffer at the offset <offset>. If
556 * <last_read> is set, no more data are expected.
557 */
558static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
559 unsigned int offset, int last_read)
560{
561 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
562 enum healthcheck_status status;
563 struct buffer *msg = NULL;
564 struct ist desc = IST_NULL;
565 unsigned int err = 0, plen = 0;
566
567
Christopher Faulet147b8c92021-04-10 09:00:38 +0200568 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
569
Willy Tarreau51cd5952020-06-05 12:25:38 +0200570 /* 3 Bytes for the packet length and 1 byte for the sequence id */
571 if (b_data(&check->bi) < offset+4) {
572 if (!last_read)
573 goto wait_more_data;
574
575 /* invalid length or truncated response */
576 status = HCHK_STATUS_L7RSP;
577 goto error;
578 }
579
580 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
581 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
582 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
583
584 if (b_data(&check->bi) < offset+plen+4) {
585 if (!last_read)
586 goto wait_more_data;
587
588 /* invalid length or truncated response */
589 status = HCHK_STATUS_L7RSP;
590 goto error;
591 }
592
593 if (*b_peek(&check->bi, offset+4) == '\xff') {
594 /* MySQL Error packet always begin with field_count = 0xff */
595 status = HCHK_STATUS_L7STS;
596 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
597 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
598 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
599 goto error;
600 }
601
602 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
603 /* Not the last rule, continue */
604 goto out;
605 }
606
607 /* We set the MySQL Version in description for information purpose
608 * FIXME : it can be cool to use MySQL Version for other purpose,
609 * like mark as down old MySQL server.
610 */
611 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
612 set_server_check_status(check, status, b_peek(&check->bi, 5));
613
614 out:
615 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200616 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200617 return ret;
618
619 error:
620 ret = TCPCHK_EVAL_STOP;
621 check->code = err;
622 msg = alloc_trash_chunk();
623 if (msg)
624 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
625 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
626 goto out;
627
628 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200629 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200630 ret = TCPCHK_EVAL_WAIT;
631 goto out;
632}
633
634/* Custom tcp-check expect function to parse and validate the MySQL initial
635 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
636 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
637 * error occurred.
638 */
639enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
640{
641 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
642}
643
644/* Custom tcp-check expect function to parse and validate the MySQL OK packet
645 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
646 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
647 * an error occurred.
648 */
649enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
650{
651 unsigned int hslen = 0;
652
653 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
654 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
655 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
656
657 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
658}
659
660/* Custom tcp-check expect function to parse and validate the LDAP bind response
661 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
662 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
663 * error occurred.
664 */
665enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
666{
667 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
668 enum healthcheck_status status;
669 struct buffer *msg = NULL;
670 struct ist desc = IST_NULL;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200671 char *ptr;
672 unsigned short nbytes = 0;
673 size_t msglen = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200674
Christopher Faulet147b8c92021-04-10 09:00:38 +0200675 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
676
Willy Tarreau51cd5952020-06-05 12:25:38 +0200677 /* Check if the server speaks LDAP (ASN.1/BER)
678 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
679 * http://tools.ietf.org/html/rfc4511
680 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200681 ptr = b_head(&check->bi) + 1;
682
Willy Tarreau51cd5952020-06-05 12:25:38 +0200683 /* size of LDAPMessage */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200684 if (*ptr & 0x80) {
685 /* For message size encoded on several bytes, we only handle
686 * size encoded on 2 or 4 bytes. There is no reason to make this
687 * part to complex because only Active Directory is known to
688 * encode BindReponse length on 4 bytes.
689 */
690 nbytes = (*ptr & 0x7f);
691 if (b_data(&check->bi) < 1 + nbytes)
692 goto too_short;
693 switch (nbytes) {
694 case 4: msglen = read_n32(ptr+1); break;
695 case 2: msglen = read_n16(ptr+1); break;
696 default:
697 status = HCHK_STATUS_L7RSP;
698 desc = ist("Not LDAPv3 protocol");
699 goto error;
700 }
701 }
702 else
703 msglen = *ptr;
704 ptr += 1 + nbytes;
705
706 if (b_data(&check->bi) < 2 + nbytes + msglen)
707 goto too_short;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200708
709 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
710 * messageID: 0x02 0x01 0x01: INTEGER 1
711 * protocolOp: 0x61: bindResponse
712 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200713 if (memcmp(ptr, "\x02\x01\x01\x61", 4) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200714 status = HCHK_STATUS_L7RSP;
715 desc = ist("Not LDAPv3 protocol");
716 goto error;
717 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200718 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200719
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200720 /* skip size of bindResponse */
721 nbytes = 0;
722 if (*ptr & 0x80)
723 nbytes = (*ptr & 0x7f);
724 ptr += 1 + nbytes;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200725
726 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
727 * ldapResult: 0x0a 0x01: ENUMERATION
728 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200729 if (memcmp(ptr, "\x0a\x01", 2) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200730 status = HCHK_STATUS_L7RSP;
731 desc = ist("Not LDAPv3 protocol");
732 goto error;
733 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200734 ptr += 2;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200735
736 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
737 * resultCode
738 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200739 check->code = *ptr;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200740 if (check->code) {
741 status = HCHK_STATUS_L7STS;
742 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
743 goto error;
744 }
745
746 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
747 set_server_check_status(check, status, "Success");
748
749 out:
750 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200751 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200752 return ret;
753
754 error:
755 ret = TCPCHK_EVAL_STOP;
756 msg = alloc_trash_chunk();
757 if (msg)
758 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
759 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
760 goto out;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200761
762 too_short:
763 if (!last_read)
764 goto wait_more_data;
765 /* invalid length or truncated response */
766 status = HCHK_STATUS_L7RSP;
767 goto error;
768
769 wait_more_data:
770 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
771 ret = TCPCHK_EVAL_WAIT;
772 goto out;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200773}
774
775/* Custom tcp-check expect function to parse and validate the SPOP hello agent
776 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
777 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
778 */
779enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
780{
781 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
782 enum healthcheck_status status;
783 struct buffer *msg = NULL;
784 struct ist desc = IST_NULL;
785 unsigned int framesz;
786
Christopher Faulet147b8c92021-04-10 09:00:38 +0200787 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200788
789 memcpy(&framesz, b_head(&check->bi), 4);
790 framesz = ntohl(framesz);
791
792 if (!last_read && b_data(&check->bi) < (4+framesz))
793 goto wait_more_data;
794
795 memset(b_orig(&trash), 0, b_size(&trash));
796 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
797 status = HCHK_STATUS_L7RSP;
798 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
799 goto error;
800 }
801
802 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
803 set_server_check_status(check, status, "SPOA server is ok");
804
805 out:
806 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200807 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200808 return ret;
809
810 error:
811 ret = TCPCHK_EVAL_STOP;
812 msg = alloc_trash_chunk();
813 if (msg)
814 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
815 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
816 goto out;
817
818 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200819 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200820 ret = TCPCHK_EVAL_WAIT;
821 goto out;
822}
823
824/* Custom tcp-check expect function to parse and validate the agent-check
825 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
826 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
827 */
828enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
829{
830 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
831 enum healthcheck_status status = HCHK_STATUS_CHECKED;
832 const char *hs = NULL; /* health status */
833 const char *as = NULL; /* admin status */
834 const char *ps = NULL; /* performance status */
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200835 const char *sc = NULL; /* maxconn */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200836 const char *err = NULL; /* first error to report */
837 const char *wrn = NULL; /* first warning to report */
838 char *cmd, *p;
839
Christopher Faulet147b8c92021-04-10 09:00:38 +0200840 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
841
Willy Tarreau51cd5952020-06-05 12:25:38 +0200842 /* We're getting an agent check response. The agent could
843 * have been disabled in the mean time with a long check
844 * still pending. It is important that we ignore the whole
845 * response.
846 */
847 if (!(check->state & CHK_ST_ENABLED))
848 goto out;
849
850 /* The agent supports strings made of a single line ended by the
851 * first CR ('\r') or LF ('\n'). This line is composed of words
852 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
853 * line may optionally contained a description of a state change
854 * after a sharp ('#'), which is only considered if a health state
855 * is announced.
856 *
857 * Words may be composed of :
858 * - a numeric weight suffixed by the percent character ('%').
859 * - a health status among "up", "down", "stopped", and "fail".
860 * - an admin status among "ready", "drain", "maint".
861 *
862 * These words may appear in any order. If multiple words of the
863 * same category appear, the last one wins.
864 */
865
866 p = b_head(&check->bi);
867 while (*p && *p != '\n' && *p != '\r')
868 p++;
869
870 if (!*p) {
871 if (!last_read)
872 goto wait_more_data;
873
874 /* at least inform the admin that the agent is mis-behaving */
875 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
876 goto out;
877 }
878
879 *p = 0;
880 cmd = b_head(&check->bi);
881
882 while (*cmd) {
883 /* look for next word */
884 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
885 cmd++;
886 continue;
887 }
888
889 if (*cmd == '#') {
890 /* this is the beginning of a health status description,
891 * skip the sharp and blanks.
892 */
893 cmd++;
894 while (*cmd == '\t' || *cmd == ' ')
895 cmd++;
896 break;
897 }
898
899 /* find the end of the word so that we have a null-terminated
900 * word between <cmd> and <p>.
901 */
902 p = cmd + 1;
903 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
904 p++;
905 if (*p)
906 *p++ = 0;
907
908 /* first, health statuses */
909 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100910 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200911 status = HCHK_STATUS_L7OKD;
912 hs = cmd;
913 }
914 else if (strcasecmp(cmd, "down") == 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 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100920 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200921 status = HCHK_STATUS_L7STS;
922 hs = cmd;
923 }
924 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100925 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200926 status = HCHK_STATUS_L7STS;
927 hs = cmd;
928 }
929 /* admin statuses */
930 else if (strcasecmp(cmd, "ready") == 0) {
931 as = cmd;
932 }
933 else if (strcasecmp(cmd, "drain") == 0) {
934 as = cmd;
935 }
936 else if (strcasecmp(cmd, "maint") == 0) {
937 as = cmd;
938 }
939 /* try to parse a weight here and keep the last one */
940 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
941 ps = cmd;
942 }
943 /* try to parse a maxconn here */
944 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200945 sc = cmd;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200946 }
947 else {
948 /* keep a copy of the first error */
949 if (!err)
950 err = cmd;
951 }
952 /* skip to next word */
953 cmd = p;
954 }
955 /* here, cmd points either to \0 or to the beginning of a
956 * description. Skip possible leading spaces.
957 */
958 while (*cmd == ' ' || *cmd == '\n')
959 cmd++;
960
961 /* First, update the admin status so that we avoid sending other
962 * possibly useless warnings and can also update the health if
963 * present after going back up.
964 */
965 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200966 if (strcasecmp(as, "drain") == 0) {
967 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200968 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200969 }
970 else if (strcasecmp(as, "maint") == 0) {
971 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200972 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200973 }
974 else {
975 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200976 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200977 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200978 }
979
980 /* now change weights */
981 if (ps) {
982 const char *msg;
983
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500984 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200985 msg = server_parse_weight_change_request(check->server, ps);
986 if (!wrn || !*wrn)
987 wrn = msg;
988 }
989
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200990 if (sc) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200991 const char *msg;
992
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200993 sc += strlen("maxconn:");
Willy Tarreau51cd5952020-06-05 12:25:38 +0200994
Christopher Faulet147b8c92021-04-10 09:00:38 +0200995 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury Denoyelle02742862021-06-18 11:11:36 +0200996 /* This is safe to call server_parse_maxconn_change_request
997 * because the server lock is held during the check.
998 */
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200999 msg = server_parse_maxconn_change_request(check->server, sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001000 if (!wrn || !*wrn)
1001 wrn = msg;
1002 }
1003
1004 /* and finally health status */
1005 if (hs) {
1006 /* We'll report some of the warnings and errors we have
1007 * here. Down reports are critical, we leave them untouched.
1008 * Lack of report, or report of 'UP' leaves the room for
1009 * ERR first, then WARN.
1010 */
1011 const char *msg = cmd;
1012 struct buffer *t;
1013
1014 if (!*msg || status == HCHK_STATUS_L7OKD) {
1015 if (err && *err)
1016 msg = err;
1017 else if (wrn && *wrn)
1018 msg = wrn;
1019 }
1020
1021 t = get_trash_chunk();
1022 chunk_printf(t, "via agent : %s%s%s%s",
1023 hs, *msg ? " (" : "",
1024 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001025 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001026 set_server_check_status(check, status, t->area);
1027 }
1028 else if (err && *err) {
1029 /* No status change but we'd like to report something odd.
1030 * Just report the current state and copy the message.
1031 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001032 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001033 chunk_printf(&trash, "agent reports an error : %s", err);
1034 set_server_check_status(check, status/*check->status*/, trash.area);
1035 }
1036 else if (wrn && *wrn) {
1037 /* No status change but we'd like to report something odd.
1038 * Just report the current state and copy the message.
1039 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001040 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001041 chunk_printf(&trash, "agent warns : %s", wrn);
1042 set_server_check_status(check, status/*check->status*/, trash.area);
1043 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001044 else {
1045 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001046 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001047 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001048
1049 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001050 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001051 return ret;
1052
1053 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001054 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001055 ret = TCPCHK_EVAL_WAIT;
1056 goto out;
1057}
1058
1059/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1060 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1061 * TCPCHK_EVAL_STOP if an error occurred.
1062 */
1063enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1064{
1065 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1066 struct tcpcheck_connect *connect = &rule->connect;
1067 struct proxy *proxy = check->proxy;
1068 struct server *s = check->server;
1069 struct task *t = check->task;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001070 struct connection *conn = sc_conn(check->sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001071 struct protocol *proto;
1072 struct xprt_ops *xprt;
1073 struct tcpcheck_rule *next;
1074 int status, port;
1075
Christopher Faulet147b8c92021-04-10 09:00:38 +02001076 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1077
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001078 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1079
1080 /* current connection already created, check if it is established or not */
1081 if (conn) {
1082 if (conn->flags & CO_FL_WAIT_XPRT) {
1083 /* We are still waiting for the connection establishment */
1084 if (next && next->action == TCPCHK_ACT_SEND) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001085 if (!(check->sc->wait_event.events & SUB_RETRY_SEND))
1086 conn->mux->subscribe(check->sc, SUB_RETRY_SEND, &check->sc->wait_event);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001087 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001088 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001089 }
1090 else
1091 ret = tcpcheck_eval_recv(check, rule);
1092 }
1093 goto out;
1094 }
1095
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001096 /* Note: here check->sc = sc = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001097
Christopher Fauletb381a502020-11-25 13:47:00 +01001098 /* Always release input and output buffer when a new connect is evaluated */
1099 check_release_buf(check, &check->bi);
1100 check_release_buf(check, &check->bo);
1101
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001102 /* No connection, prepare a new one */
Christopher Fauletdd2d0d82021-12-20 09:34:32 +01001103 conn = conn_new((s ? &s->obj_type : &proxy->obj_type));
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001104 if (!conn) {
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001105 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1106 tcpcheck_get_step_id(check, rule));
1107 if (rule->comment)
1108 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1109 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1110 ret = TCPCHK_EVAL_STOP;
Willy Tarreau4596fe22022-05-17 19:07:51 +02001111 TRACE_ERROR("stconn allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001112 goto out;
1113 }
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001114 if (sc_attach_mux(check->sc, NULL, conn) < 0) {
Christopher Faulet070b91b2022-03-31 19:27:18 +02001115 TRACE_ERROR("mux attach error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
1116 conn_free(conn);
1117 conn = NULL;
1118 status = SF_ERR_RESOURCE;
1119 goto fail_check;
1120 }
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001121 conn->ctx = check->sc;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001122 conn_set_owner(conn, check->sess, NULL);
1123
Willy Tarreau51cd5952020-06-05 12:25:38 +02001124 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001125 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001126 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001127 status = SF_ERR_RESOURCE;
1128 goto fail_check;
1129 }
1130
1131 /* connect to the connect rule addr if specified, otherwise the check
1132 * addr if specified on the server. otherwise, use the server addr (it
1133 * MUST exist at this step).
1134 */
1135 *conn->dst = (is_addr(&connect->addr)
1136 ? connect->addr
1137 : (is_addr(&check->addr) ? check->addr : s->addr));
Willy Tarreau14e7f292021-10-27 17:41:07 +02001138 proto = protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001139
1140 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001141 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001142 port = connect->port;
1143 if (!port && connect->port_expr) {
1144 struct sample *smp;
1145
1146 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1147 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1148 connect->port_expr, SMP_T_SINT);
1149 if (smp)
1150 port = smp->data.u.sint;
1151 }
1152 if (!port && is_inet_addr(&connect->addr))
1153 port = get_host_port(&connect->addr);
1154 if (!port && check->port)
1155 port = check->port;
1156 if (!port && is_inet_addr(&check->addr))
1157 port = get_host_port(&check->addr);
1158 if (!port) {
1159 /* The server MUST exist here */
1160 port = s->svc_port;
1161 }
1162 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001163 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001164
1165 xprt = ((connect->options & TCPCHK_OPT_SSL)
1166 ? xprt_get(XPRT_SSL)
1167 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1168
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001169 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001170 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001171 status = SF_ERR_RESOURCE;
1172 goto fail_check;
1173 }
1174
Christopher Fauletf7177272020-10-02 13:41:55 +02001175 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1176 conn->send_proxy_ofs = 1;
1177 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001178 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001179 }
1180 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1181 conn->send_proxy_ofs = 1;
1182 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001183 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001184 }
1185
1186 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1187 conn->send_proxy_ofs = 1;
1188 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001189 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001190 }
1191 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1192 conn->send_proxy_ofs = 1;
1193 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001194 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001195 }
1196
Willy Tarreau51cd5952020-06-05 12:25:38 +02001197 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001198 if (proto && proto->connect) {
1199 int flags = 0;
1200
Christopher Fauletf6112482022-08-30 10:31:15 +02001201 if (!next)
1202 flags |= CONNECT_DELACK_ALWAYS;
Christopher Faulet871dd822022-08-24 11:38:03 +02001203 if (connect->options & TCPCHK_OPT_HAS_DATA)
Christopher Fauletf6112482022-08-30 10:31:15 +02001204 flags |= (CONNECT_HAS_DATA|CONNECT_DELACK_ALWAYS);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001205 status = proto->connect(conn, flags);
1206 }
1207
1208 if (status != SF_ERR_NONE)
1209 goto fail_check;
1210
Christopher Faulet21ddc742020-07-01 15:26:14 +02001211 conn_set_private(conn);
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001212 conn->ctx = check->sc;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001213
Willy Tarreau51cd5952020-06-05 12:25:38 +02001214#ifdef USE_OPENSSL
1215 if (connect->sni)
1216 ssl_sock_set_servername(conn, connect->sni);
1217 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1218 ssl_sock_set_servername(conn, s->check.sni);
1219
1220 if (connect->alpn)
1221 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1222 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1223 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1224#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001225
Willy Tarreaue2226792022-04-11 18:04:33 +02001226 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER) && !(conn->flags & CO_FL_FDLESS)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001227 /* Some servers don't like reset on close */
Christopher Faulet897d6122021-12-17 17:28:35 +01001228 HA_ATOMIC_AND(&fdtab[conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001229 }
1230
1231 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1232 if (xprt_add_hs(conn) < 0)
1233 status = SF_ERR_RESOURCE;
1234 }
1235
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001236 if (conn_xprt_start(conn) < 0) {
1237 status = SF_ERR_RESOURCE;
1238 goto fail_check;
1239 }
1240
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001241 /* The mux may be initialized now if there isn't server attached to the
1242 * check (email alerts) or if there is a mux proto specified or if there
1243 * is no alpn.
1244 */
1245 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1246 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1247 const struct mux_ops *mux_ops;
1248
Christopher Faulet147b8c92021-04-10 09:00:38 +02001249 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001250 if (connect->mux_proto)
1251 mux_ops = connect->mux_proto->mux;
1252 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1253 mux_ops = check->mux_proto->mux;
1254 else {
1255 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1256 ? PROTO_MODE_HTTP
1257 : PROTO_MODE_TCP);
1258
1259 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1260 }
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001261 if (mux_ops && conn_install_mux(conn, mux_ops, check->sc, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001262 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001263 status = SF_ERR_INTERNAL;
1264 goto fail_check;
1265 }
1266 }
1267
Willy Tarreau51cd5952020-06-05 12:25:38 +02001268 fail_check:
1269 /* It can return one of :
1270 * - SF_ERR_NONE if everything's OK
1271 * - SF_ERR_SRVTO if there are no more servers
1272 * - SF_ERR_SRVCL if the connection was refused by the server
1273 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1274 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1275 * - SF_ERR_INTERNAL for any other purely internal errors
1276 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1277 * Note that we try to prevent the network stack from sending the ACK during the
1278 * connect() when a pure TCP check is used (without PROXY protocol).
1279 */
1280 switch (status) {
1281 case SF_ERR_NONE:
1282 /* we allow up to min(inter, timeout.connect) for a connection
1283 * to establish but only when timeout.check is set as it may be
1284 * to short for a full check otherwise
1285 */
1286 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1287
1288 if (proxy->timeout.check && proxy->timeout.connect) {
1289 int t_con = tick_add(now_ms, proxy->timeout.connect);
1290 t->expire = tick_first(t->expire, t_con);
1291 }
1292 break;
1293 case SF_ERR_SRVTO: /* ETIMEDOUT */
1294 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1295 case SF_ERR_PRXCOND:
1296 case SF_ERR_RESOURCE:
1297 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001298 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 +02001299 chk_report_conn_err(check, errno, 0);
1300 ret = TCPCHK_EVAL_STOP;
1301 goto out;
1302 }
1303
1304 /* don't do anything until the connection is established */
1305 if (conn->flags & CO_FL_WAIT_XPRT) {
1306 if (conn->mux) {
1307 if (next && next->action == TCPCHK_ACT_SEND)
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001308 conn->mux->subscribe(check->sc, SUB_RETRY_SEND, &check->sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001309 else
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001310 conn->mux->subscribe(check->sc, SUB_RETRY_RECV, &check->sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001311 }
1312 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001313 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001314 goto out;
1315 }
1316
1317 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001318 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001319 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001320 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001321 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001322
1323 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1324 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1325
Christopher Faulet147b8c92021-04-10 09:00:38 +02001326 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001327 return ret;
1328}
1329
1330/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1331 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1332 * TCPCHK_EVAL_STOP if an error occurred.
1333 */
1334enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1335{
1336 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1337 struct tcpcheck_send *send = &rule->send;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001338 struct stconn *sc = check->sc;
1339 struct connection *conn = __sc_conn(sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001340 struct buffer *tmp = NULL;
1341 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001342 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001343
Christopher Faulet147b8c92021-04-10 09:00:38 +02001344 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1345
Christopher Fauletb381a502020-11-25 13:47:00 +01001346 if (check->state & CHK_ST_OUT_ALLOC) {
1347 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001348 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001349 goto out;
1350 }
1351
1352 if (!check_get_buf(check, &check->bo)) {
1353 check->state |= CHK_ST_OUT_ALLOC;
1354 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001355 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 +01001356 goto out;
1357 }
1358
Christopher Faulet39066c22020-11-25 13:34:51 +01001359 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001360 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 +02001361 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 +01001362 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001363 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001364
Christopher Fauletb381a502020-11-25 13:47:00 +01001365 /* Always release input buffer when a new send is evaluated */
1366 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001367
1368 switch (send->type) {
1369 case TCPCHK_SEND_STRING:
1370 case TCPCHK_SEND_BINARY:
1371 if (istlen(send->data) >= b_size(&check->bo)) {
1372 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1373 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1374 tcpcheck_get_step_id(check, rule));
1375 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1376 ret = TCPCHK_EVAL_STOP;
1377 goto out;
1378 }
1379 b_putist(&check->bo, send->data);
1380 break;
1381 case TCPCHK_SEND_STRING_LF:
1382 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1383 if (!b_data(&check->bo))
1384 goto out;
1385 break;
1386 case TCPCHK_SEND_BINARY_LF: {
1387 int len = b_size(&check->bo);
1388
1389 tmp = alloc_trash_chunk();
1390 if (!tmp)
1391 goto error_lf;
1392 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1393 if (!b_data(tmp))
1394 goto out;
1395 tmp->area[tmp->data] = '\0';
1396 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1397 goto error_lf;
1398 check->bo.data = len;
1399 break;
1400 }
1401 case TCPCHK_SEND_HTTP: {
1402 struct htx_sl *sl;
1403 struct ist meth, uri, vsn, clen, body;
1404 unsigned int slflags = 0;
1405
1406 tmp = alloc_trash_chunk();
1407 if (!tmp)
1408 goto error_htx;
1409
1410 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1411 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1412 : http_known_methods[send->http.meth.meth]);
1413 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1414 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1415 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1416 }
1417 else
1418 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1419 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1420
1421 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1422 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1423 slflags |= HTX_SL_F_VER_11;
1424 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
Christopher Faulet0506d9d2023-02-28 18:44:14 +01001425 if (!(send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !isttest(send->http.body))
Willy Tarreau51cd5952020-06-05 12:25:38 +02001426 slflags |= HTX_SL_F_BODYLESS;
1427
1428 htx = htx_from_buf(&check->bo);
1429 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1430 if (!sl)
1431 goto error_htx;
1432 sl->info.req.meth = send->http.meth.meth;
1433 if (!http_update_host(htx, sl, uri))
1434 goto error_htx;
1435
1436 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1437 struct tcpcheck_http_hdr *hdr;
1438 struct ist hdr_value;
1439
1440 list_for_each_entry(hdr, &send->http.hdrs, list) {
1441 chunk_reset(tmp);
1442 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1443 if (!b_data(tmp))
1444 continue;
1445 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1446 if (!htx_add_header(htx, hdr->name, hdr_value))
1447 goto error_htx;
1448 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1449 if (!http_update_authority(htx, sl, hdr_value))
1450 goto error_htx;
1451 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001452 if (isteqi(hdr->name, ist("connection")))
1453 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001454 }
1455
1456 }
1457 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1458 chunk_reset(tmp);
1459 httpchk_build_status_header(check->server, tmp);
1460 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1461 goto error_htx;
1462 }
1463
1464
1465 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1466 chunk_reset(tmp);
1467 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1468 body = ist2(b_orig(tmp), b_data(tmp));
1469 }
1470 else
1471 body = send->http.body;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001472
Christopher Fauletd48bfb62023-02-28 18:51:26 +01001473 if (!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close")))
Willy Tarreau51cd5952020-06-05 12:25:38 +02001474 goto error_htx;
1475
Christopher Fauletd48bfb62023-02-28 18:51:26 +01001476 if ((send->http.meth.meth != HTTP_METH_OPTIONS &&
1477 send->http.meth.meth != HTTP_METH_GET &&
1478 send->http.meth.meth != HTTP_METH_HEAD &&
1479 send->http.meth.meth != HTTP_METH_DELETE) || istlen(body)) {
1480 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1481 if (!htx_add_header(htx, ist("Content-length"), clen))
1482 goto error_htx;
1483 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001484
1485 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001486 (istlen(body) && !htx_add_data_atonce(htx, body)))
1487 goto error_htx;
1488
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001489 /* no more data are expected */
1490 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001491 htx_to_buf(htx, &check->bo);
1492 break;
1493 }
1494 case TCPCHK_SEND_UNDEF:
1495 /* Should never happen. */
1496 ret = TCPCHK_EVAL_STOP;
1497 goto out;
1498 };
1499
Christopher Faulet39066c22020-11-25 13:34:51 +01001500 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001501 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001502 if (conn->mux->snd_buf(sc, &check->bo,
Willy Tarreau51cd5952020-06-05 12:25:38 +02001503 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001504 if ((conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001505 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001506 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 +02001507 goto out;
1508 }
1509 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001510 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 +02001511 conn->mux->subscribe(sc, SUB_RETRY_SEND, &sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001512 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001513 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001514 goto out;
1515 }
1516
1517 out:
1518 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001519 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1520 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001521
1522 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001523 return ret;
1524
1525 error_htx:
1526 if (htx) {
1527 htx_reset(htx);
1528 htx_to_buf(htx, &check->bo);
1529 }
1530 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1531 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001532 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 +02001533 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1534 ret = TCPCHK_EVAL_STOP;
1535 goto out;
1536
1537 error_lf:
1538 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1539 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001540 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 +02001541 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1542 ret = TCPCHK_EVAL_STOP;
1543 goto out;
1544
1545}
1546
1547/* Try to receive data before evaluating a tcp-check expect rule. Returns
1548 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1549 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1550 * TCPCHK_EVAL_STOP if an error occurred.
1551 */
1552enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1553{
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001554 struct stconn *sc = check->sc;
1555 struct connection *conn = __sc_conn(sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001556 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1557 size_t max, read, cur_read = 0;
1558 int is_empty;
1559 int read_poll = MAX_READ_POLL_LOOPS;
1560
Christopher Faulet147b8c92021-04-10 09:00:38 +02001561 TRACE_ENTER(CHK_EV_RX_DATA, check);
1562
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001563 if (sc->wait_event.events & SUB_RETRY_RECV) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001564 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001565 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001566 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001567
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001568 if (sc_ep_test(sc, SE_FL_EOS))
Willy Tarreau51cd5952020-06-05 12:25:38 +02001569 goto end_recv;
1570
Christopher Faulet147b8c92021-04-10 09:00:38 +02001571 if (check->state & CHK_ST_IN_ALLOC) {
1572 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001573 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001574 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001575
1576 if (!check_get_buf(check, &check->bi)) {
1577 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001578 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001579 goto wait_more_data;
1580 }
1581
Willy Tarreau4596fe22022-05-17 19:07:51 +02001582 /* errors on the connection and the stream connector were already checked */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001583
1584 /* prepare to detect if the mux needs more room */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001585 sc_ep_clr(sc, SE_FL_WANT_ROOM);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001586
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001587 while (sc_ep_test(sc, SE_FL_RCV_MORE) ||
1588 (!(conn->flags & CO_FL_ERROR) && !sc_ep_test(sc, SE_FL_ERROR | SE_FL_EOS))) {
1589 max = (IS_HTX_SC(sc) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1590 read = conn->mux->rcv_buf(sc, &check->bi, max, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001591 cur_read += read;
1592 if (!read ||
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001593 sc_ep_test(sc, SE_FL_WANT_ROOM) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001594 (--read_poll <= 0) ||
1595 (read < max && read >= global.tune.recv_enough))
1596 break;
1597 }
1598
1599 end_recv:
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001600 is_empty = (IS_HTX_SC(sc) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1601 if (is_empty && ((conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001602 /* Report network errors only if we got no other data. Otherwise
1603 * we'll let the upper layers decide whether the response is OK
1604 * or not. It is very common that an RST sent by the server is
1605 * reported as an error just after the last data chunk.
1606 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001607 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001608 goto stop;
1609 }
Christopher Fauleta664aa62023-03-20 16:49:51 +01001610 else if (!cur_read && !sc_ep_test(sc, SE_FL_WANT_ROOM | SE_FL_ERROR | SE_FL_EOS)) {
1611 conn->mux->subscribe(sc, SUB_RETRY_RECV, &sc->wait_event);
1612 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
1613 goto wait_more_data;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001614 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001615 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001616
1617 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001618 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1619 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001620
1621 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001622 return ret;
1623
1624 stop:
1625 ret = TCPCHK_EVAL_STOP;
1626 goto out;
1627
1628 wait_more_data:
1629 ret = TCPCHK_EVAL_WAIT;
1630 goto out;
1631}
1632
1633/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1634 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1635 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1636 * error occurred.
1637 */
1638enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1639{
1640 struct htx *htx = htxbuf(&check->bi);
1641 struct htx_sl *sl;
1642 struct htx_blk *blk;
1643 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1644 struct tcpcheck_expect *expect = &rule->expect;
1645 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1646 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1647 struct ist desc = IST_NULL;
1648 int i, match, inverse;
1649
Christopher Faulet147b8c92021-04-10 09:00:38 +02001650 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1651
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001652 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001653
1654 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001655 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001656 status = HCHK_STATUS_L7RSP;
1657 goto error;
1658 }
1659
1660 if (htx_is_empty(htx)) {
1661 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001662 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001663 status = HCHK_STATUS_L7RSP;
1664 goto error;
1665 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001666 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001667 goto wait_more_data;
1668 }
1669
1670 sl = http_get_stline(htx);
1671 check->code = sl->info.res.status;
1672
1673 if (check->server &&
1674 (check->server->proxy->options & PR_O_DISABLE404) &&
1675 (check->server->next_state != SRV_ST_STOPPED) &&
1676 (check->code == 404)) {
1677 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001678 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001679 goto out;
1680 }
1681
1682 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1683 /* Make GCC happy ; initialize match to a failure state. */
1684 match = inverse;
1685 status = expect->err_status;
1686
1687 switch (expect->type) {
1688 case TCPCHK_EXPECT_HTTP_STATUS:
1689 match = 0;
1690 for (i = 0; i < expect->codes.num; i++) {
1691 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1692 sl->info.res.status <= expect->codes.codes[i][1]) {
1693 match = 1;
1694 break;
1695 }
1696 }
1697
1698 /* Set status and description in case of error */
1699 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1700 if (LIST_ISEMPTY(&expect->onerror_fmt))
1701 desc = htx_sl_res_reason(sl);
1702 break;
1703 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1704 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1705
1706 /* Set status and description in case of error */
1707 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1708 if (LIST_ISEMPTY(&expect->onerror_fmt))
1709 desc = htx_sl_res_reason(sl);
1710 break;
1711
1712 case TCPCHK_EXPECT_HTTP_HEADER: {
1713 struct http_hdr_ctx ctx;
1714 struct ist npat, vpat, value;
1715 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1716
1717 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1718 nbuf = alloc_trash_chunk();
1719 if (!nbuf) {
1720 status = HCHK_STATUS_L7RSP;
1721 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001722 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001723 goto error;
1724 }
1725 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1726 if (!b_data(nbuf)) {
1727 status = HCHK_STATUS_L7RSP;
1728 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001729 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001730 goto error;
1731 }
1732 npat = ist2(b_orig(nbuf), b_data(nbuf));
1733 }
1734 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1735 npat = expect->hdr.name;
1736
1737 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1738 vbuf = alloc_trash_chunk();
1739 if (!vbuf) {
1740 status = HCHK_STATUS_L7RSP;
1741 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001742 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001743 goto error;
1744 }
1745 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1746 if (!b_data(vbuf)) {
1747 status = HCHK_STATUS_L7RSP;
1748 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001749 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001750 goto error;
1751 }
1752 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1753 }
1754 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1755 vpat = expect->hdr.value;
1756
1757 match = 0;
1758 ctx.blk = NULL;
1759 while (1) {
1760 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1761 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1762 if (!http_find_str_header(htx, npat, &ctx, full))
1763 goto end_of_match;
1764 break;
1765 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1766 if (!http_find_pfx_header(htx, npat, &ctx, full))
1767 goto end_of_match;
1768 break;
1769 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1770 if (!http_find_sfx_header(htx, npat, &ctx, full))
1771 goto end_of_match;
1772 break;
1773 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1774 if (!http_find_sub_header(htx, npat, &ctx, full))
1775 goto end_of_match;
1776 break;
1777 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1778 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1779 goto end_of_match;
1780 break;
1781 default:
1782 /* should never happen */
1783 goto end_of_match;
1784 }
1785
1786 /* A header has matched the name pattern, let's test its
1787 * value now (always defined from there). If there is no
1788 * value pattern, it is a good match.
1789 */
1790
1791 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1792 match = 1;
1793 goto end_of_match;
1794 }
1795
1796 value = ctx.value;
1797 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1798 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1799 if (isteq(value, vpat)) {
1800 match = 1;
1801 goto end_of_match;
1802 }
1803 break;
1804 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1805 if (istlen(value) < istlen(vpat))
1806 break;
1807 value = ist2(istptr(value), istlen(vpat));
1808 if (isteq(value, vpat)) {
1809 match = 1;
1810 goto end_of_match;
1811 }
1812 break;
1813 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1814 if (istlen(value) < istlen(vpat))
1815 break;
Tim Duesterhus4c8f75f2021-11-06 15:14:44 +01001816 value = ist2(istend(value) - istlen(vpat), istlen(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001817 if (isteq(value, vpat)) {
1818 match = 1;
1819 goto end_of_match;
1820 }
1821 break;
1822 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1823 if (isttest(istist(value, vpat))) {
1824 match = 1;
1825 goto end_of_match;
1826 }
1827 break;
1828 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1829 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1830 match = 1;
1831 goto end_of_match;
1832 }
1833 break;
1834 }
1835 }
1836
1837 end_of_match:
1838 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1839 if (LIST_ISEMPTY(&expect->onerror_fmt))
1840 desc = htx_sl_res_reason(sl);
1841 break;
1842 }
1843
1844 case TCPCHK_EXPECT_HTTP_BODY:
1845 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1846 case TCPCHK_EXPECT_HTTP_BODY_LF:
1847 match = 0;
1848 chunk_reset(&trash);
1849 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1850 enum htx_blk_type type = htx_get_blk_type(blk);
1851
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001852 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001853 break;
1854 if (type == HTX_BLK_DATA) {
1855 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1856 break;
1857 }
1858 }
1859
1860 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001861 if (!last_read) {
1862 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001863 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001864 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001865 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1866 if (LIST_ISEMPTY(&expect->onerror_fmt))
1867 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001868 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001869 goto error;
1870 }
1871
1872 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1873 tmp = alloc_trash_chunk();
1874 if (!tmp) {
1875 status = HCHK_STATUS_L7RSP;
1876 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001877 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001878 goto error;
1879 }
1880 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1881 if (!b_data(tmp)) {
1882 status = HCHK_STATUS_L7RSP;
1883 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001884 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001885 goto error;
1886 }
1887 }
1888
1889 if (!last_read &&
1890 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1891 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1892 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1893 ret = TCPCHK_EVAL_WAIT;
1894 goto out;
1895 }
1896
1897 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1898 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1899 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1900 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1901 else
1902 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1903
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001904 /* Wait for more data on mismatch only if no minimum is defined (-1),
1905 * otherwise the absence of match is already conclusive.
1906 */
1907 if (!match && !last_read && (expect->min_recv == -1)) {
1908 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001909 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001910 goto out;
1911 }
1912
Willy Tarreau51cd5952020-06-05 12:25:38 +02001913 /* Set status and description in case of error */
1914 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1915 if (LIST_ISEMPTY(&expect->onerror_fmt))
1916 desc = (inverse
1917 ? ist("HTTP check matched unwanted content")
1918 : ist("HTTP content check did not match"));
1919 break;
1920
1921
1922 default:
1923 /* should never happen */
1924 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1925 goto error;
1926 }
1927
Christopher Faulet147b8c92021-04-10 09:00:38 +02001928 if (!(match ^ inverse)) {
1929 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001930 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001931 }
1932
1933 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001934
1935 out:
1936 free_trash_chunk(tmp);
1937 free_trash_chunk(nbuf);
1938 free_trash_chunk(vbuf);
1939 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001940 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001941 return ret;
1942
1943 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001944 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001945 ret = TCPCHK_EVAL_STOP;
1946 msg = alloc_trash_chunk();
1947 if (msg)
1948 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1949 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1950 goto out;
1951
1952 wait_more_data:
1953 ret = TCPCHK_EVAL_WAIT;
1954 goto out;
1955}
1956
1957/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1958 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1959 * if an error occurred.
1960 */
1961enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1962{
1963 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1964 struct tcpcheck_expect *expect = &rule->expect;
1965 struct buffer *msg = NULL, *tmp = NULL;
1966 struct ist desc = IST_NULL;
1967 enum healthcheck_status status;
1968 int match, inverse;
1969
Christopher Faulet147b8c92021-04-10 09:00:38 +02001970 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1971
Willy Tarreau51cd5952020-06-05 12:25:38 +02001972 last_read |= b_full(&check->bi);
1973
1974 /* The current expect might need more data than the previous one, check again
1975 * that the minimum amount data required to match is respected.
1976 */
1977 if (!last_read) {
1978 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1979 (b_data(&check->bi) < istlen(expect->data))) {
1980 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001981 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001982 goto out;
1983 }
1984 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1985 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001986 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001987 goto out;
1988 }
1989 }
1990
1991 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1992 /* Make GCC happy ; initialize match to a failure state. */
1993 match = inverse;
1994 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1995
1996 switch (expect->type) {
1997 case TCPCHK_EXPECT_STRING:
1998 case TCPCHK_EXPECT_BINARY:
1999 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2000 break;
2001 case TCPCHK_EXPECT_STRING_REGEX:
2002 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2003 break;
2004
2005 case TCPCHK_EXPECT_BINARY_REGEX:
2006 chunk_reset(&trash);
2007 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2008 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2009 break;
2010
2011 case TCPCHK_EXPECT_STRING_LF:
2012 case TCPCHK_EXPECT_BINARY_LF:
2013 match = 0;
2014 tmp = alloc_trash_chunk();
2015 if (!tmp) {
2016 status = HCHK_STATUS_L7RSP;
2017 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002018 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002019 goto error;
2020 }
2021 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2022 if (!b_data(tmp)) {
2023 status = HCHK_STATUS_L7RSP;
2024 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002025 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002026 goto error;
2027 }
2028 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2029 int len = tmp->data;
2030 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2031 status = HCHK_STATUS_L7RSP;
2032 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002033 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002034 goto error;
2035 }
2036 tmp->data = len;
2037 }
2038 if (b_data(&check->bi) < tmp->data) {
2039 if (!last_read) {
2040 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002041 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002042 goto out;
2043 }
2044 break;
2045 }
2046 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2047 break;
2048
2049 case TCPCHK_EXPECT_CUSTOM:
Christopher Faulet2ebac6a2023-04-28 14:47:15 +02002050 /* Don't eval custom function if the buffer is empty. It means
Willy Tarreaudd9f9212023-05-07 07:07:44 +02002051 * custom functions can't expect an empty response. If this
Christopher Faulet2ebac6a2023-04-28 14:47:15 +02002052 * change, don't forget to change this test and update all
2053 * custom functions.
2054 */
2055 if (!b_data(&check->bi))
2056 break;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002057 if (expect->custom)
2058 ret = expect->custom(check, rule, last_read);
2059 goto out;
2060 default:
2061 /* Should never happen. */
2062 ret = TCPCHK_EVAL_STOP;
2063 goto out;
2064 }
2065
2066
2067 /* Wait for more data on mismatch only if no minimum is defined (-1),
2068 * otherwise the absence of match is already conclusive.
2069 */
2070 if (!match && !last_read && (expect->min_recv == -1)) {
2071 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002072 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002073 goto out;
2074 }
2075
2076 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002077 if (match ^ inverse) {
2078 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002079 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002080 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002081
2082 error:
2083 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002084 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002085 ret = TCPCHK_EVAL_STOP;
2086 msg = alloc_trash_chunk();
2087 if (msg)
2088 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2089 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2090 free_trash_chunk(msg);
2091
2092 out:
2093 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002094 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002095 return ret;
2096}
2097
2098/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2099 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2100 * waits.
2101 */
2102enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2103{
2104 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2105 struct act_rule *act_rule;
2106 enum act_return act_ret;
2107
2108 act_rule =rule->action_kw.rule;
2109 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2110 if (act_ret != ACT_RET_CONT) {
2111 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2112 tcpcheck_get_step_id(check, rule));
2113 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2114 ret = TCPCHK_EVAL_STOP;
2115 }
2116
2117 return ret;
2118}
2119
2120/* Executes a tcp-check ruleset. Note that this is called both from the
2121 * connection's wake() callback and from the check scheduling task. It returns
2122 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2123 * presenting the risk of an fd replacement.
2124 *
2125 * Please do NOT place any return statement in this function and only leave
2126 * via the out_end_tcpcheck label after setting retcode.
2127 */
2128int tcpcheck_main(struct check *check)
2129{
2130 struct tcpcheck_rule *rule;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002131 struct stconn *sc = check->sc;
2132 struct connection *conn = sc_conn(sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002133 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002134 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002135 enum tcpcheck_eval_ret eval_ret;
2136
2137 /* here, we know that the check is complete or that it failed */
2138 if (check->result != CHK_RES_UNKNOWN)
2139 goto out;
2140
Christopher Faulet147b8c92021-04-10 09:00:38 +02002141 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2142
Willy Tarreau4596fe22022-05-17 19:07:51 +02002143 /* Note: the stream connector and the connection may only be undefined before
Willy Tarreau51cd5952020-06-05 12:25:38 +02002144 * the first rule evaluation (it is always a connect rule) or when the
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002145 * stream connector allocation failed on a connect rule, during sc allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002146 */
2147
2148 /* 1- check for connection error, if any */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002149 if ((conn && conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002150 goto out_end_tcpcheck;
2151
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002152 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002153 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002154 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002155 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002156 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2157 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002158
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002159 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002160 * tcp-check variables */
2161 else {
2162 struct tcpcheck_var *var;
2163
2164 /* First evaluation, create a session */
2165 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2166 if (!check->sess) {
2167 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002168 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002169 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2170 goto out_end_tcpcheck;
2171 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002172 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002173 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2174
2175 /* Preset tcp-check variables */
2176 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2177 struct sample smp;
2178
2179 memset(&smp, 0, sizeof(smp));
2180 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2181 smp.data = var->data;
2182 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2183 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002184 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002185 }
2186
2187 /* Now evaluate the tcp-check rules */
2188
2189 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2190 check->code = 0;
2191 switch (rule->action) {
2192 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002193 /* Not the first connection, release it first */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002194 if (sc_conn(sc) && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002195 check->state |= CHK_ST_CLOSE_CONN;
2196 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002197 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002198
2199 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002200
2201 /* We are still waiting the connection gets closed */
Christopher Faulet560b8da2022-05-30 08:37:39 +02002202 if (check->state & CHK_ST_CLOSE_CONN) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002203 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002204 eval_ret = TCPCHK_EVAL_WAIT;
2205 break;
2206 }
2207
Christopher Faulet147b8c92021-04-10 09:00:38 +02002208 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002209 eval_ret = tcpcheck_eval_connect(check, rule);
2210
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002211 /* Refresh connection */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002212 conn = sc_conn(sc);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002213 last_read = 0;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002214 must_read = (IS_HTX_SC(sc) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002215 break;
2216 case TCPCHK_ACT_SEND:
2217 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002218 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002219 eval_ret = tcpcheck_eval_send(check, rule);
2220 must_read = 1;
2221 break;
2222 case TCPCHK_ACT_EXPECT:
2223 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002224 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002225 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002226 eval_ret = tcpcheck_eval_recv(check, rule);
2227 if (eval_ret == TCPCHK_EVAL_STOP)
2228 goto out_end_tcpcheck;
2229 else if (eval_ret == TCPCHK_EVAL_WAIT)
2230 goto out;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002231 last_read = ((conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR | SE_FL_EOS));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002232 must_read = 0;
2233 }
2234
2235 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2236 ? tcpcheck_eval_expect_http(check, rule, last_read)
2237 : tcpcheck_eval_expect(check, rule, last_read));
2238
2239 if (eval_ret == TCPCHK_EVAL_WAIT) {
2240 check->current_step = rule->expect.head;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002241 if (!(sc->wait_event.events & SUB_RETRY_RECV))
2242 conn->mux->subscribe(sc, SUB_RETRY_RECV, &sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002243 }
2244 break;
2245 case TCPCHK_ACT_ACTION_KW:
2246 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002247 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002248 eval_ret = tcpcheck_eval_action_kw(check, rule);
2249 break;
2250 default:
2251 /* Otherwise, just go to the next one and don't update
2252 * the current step
2253 */
2254 eval_ret = TCPCHK_EVAL_CONTINUE;
2255 break;
2256 }
2257
2258 switch (eval_ret) {
2259 case TCPCHK_EVAL_CONTINUE:
2260 break;
2261 case TCPCHK_EVAL_WAIT:
2262 goto out;
2263 case TCPCHK_EVAL_STOP:
2264 goto out_end_tcpcheck;
2265 }
2266 }
2267
2268 /* All rules was evaluated */
2269 if (check->current_step) {
2270 rule = check->current_step;
2271
Christopher Faulet147b8c92021-04-10 09:00:38 +02002272 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2273
Willy Tarreau51cd5952020-06-05 12:25:38 +02002274 if (rule->action == TCPCHK_ACT_EXPECT) {
2275 struct buffer *msg;
2276 enum healthcheck_status status;
2277
2278 if (check->server &&
2279 (check->server->proxy->options & PR_O_DISABLE404) &&
2280 (check->server->next_state != SRV_ST_STOPPED) &&
2281 (check->code == 404)) {
2282 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002283 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002284 goto out_end_tcpcheck;
2285 }
2286
2287 msg = alloc_trash_chunk();
2288 if (msg)
2289 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2290 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2291 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2292 free_trash_chunk(msg);
2293 }
2294 else if (rule->action == TCPCHK_ACT_CONNECT) {
2295 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2296 enum healthcheck_status status = HCHK_STATUS_L4OK;
2297#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002298 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002299 status = HCHK_STATUS_L6OK;
2300#endif
2301 set_server_check_status(check, status, msg);
2302 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002303 else
2304 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002305 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002306 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002307 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002308 }
2309 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002310
2311 out_end_tcpcheck:
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002312 if ((conn && conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002313 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002314 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002315 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002316
Christopher Fauletb381a502020-11-25 13:47:00 +01002317 /* the tcpcheck is finished, release in/out buffer now */
2318 check_release_buf(check, &check->bi);
2319 check_release_buf(check, &check->bo);
2320
Willy Tarreau51cd5952020-06-05 12:25:38 +02002321 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002322 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002323 return retcode;
2324}
2325
Willy Tarreaua631b862022-03-02 14:54:44 +01002326void tcp_check_keywords_register(struct action_kw_list *kw_list)
2327{
2328 LIST_APPEND(&tcp_check_keywords.list, &kw_list->list);
2329}
Willy Tarreau51cd5952020-06-05 12:25:38 +02002330
2331/**************************************************************************/
2332/******************* Internals to parse tcp-check rules *******************/
2333/**************************************************************************/
2334struct action_kw_list tcp_check_keywords = {
2335 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2336};
2337
2338/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2339 * returned on error.
2340 */
2341struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2342 struct list *rules, struct action_kw *kw,
2343 const char *file, int line, char **errmsg)
2344{
2345 struct tcpcheck_rule *chk = NULL;
2346 struct act_rule *actrule = NULL;
2347
Willy Tarreaud535f802021-10-11 08:49:26 +02002348 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002349 if (!actrule) {
2350 memprintf(errmsg, "out of memory");
2351 goto error;
2352 }
2353 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002354
2355 cur_arg++;
2356 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2357 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2358 goto error;
2359 }
2360
2361 chk = calloc(1, sizeof(*chk));
2362 if (!chk) {
2363 memprintf(errmsg, "out of memory");
2364 goto error;
2365 }
2366 chk->action = TCPCHK_ACT_ACTION_KW;
2367 chk->action_kw.rule = actrule;
2368 return chk;
2369
2370 error:
2371 free(actrule);
2372 return NULL;
2373}
2374
2375/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2376 * returned on error.
2377 */
2378struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2379 const char *file, int line, char **errmsg)
2380{
2381 struct tcpcheck_rule *chk = NULL;
2382 struct sockaddr_storage *sk = NULL;
2383 char *comment = NULL, *sni = NULL, *alpn = NULL;
2384 struct sample_expr *port_expr = NULL;
2385 const struct mux_proto_list *mux_proto = NULL;
2386 unsigned short conn_opts = 0;
2387 long port = 0;
2388 int alpn_len = 0;
2389
2390 list_for_each_entry(chk, rules, list) {
2391 if (chk->action == TCPCHK_ACT_CONNECT)
2392 break;
2393 if (chk->action == TCPCHK_ACT_COMMENT ||
2394 chk->action == TCPCHK_ACT_ACTION_KW ||
2395 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2396 continue;
2397
2398 memprintf(errmsg, "first step MUST also be a 'connect', "
2399 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2400 "when there is a 'connect' step in the tcp-check ruleset");
2401 goto error;
2402 }
2403
2404 cur_arg++;
2405 while (*(args[cur_arg])) {
2406 if (strcmp(args[cur_arg], "default") == 0)
2407 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2408 else if (strcmp(args[cur_arg], "addr") == 0) {
2409 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002410
2411 if (!*(args[cur_arg+1])) {
2412 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2413 goto error;
2414 }
2415
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002416 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2417 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002418 if (!sk) {
2419 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2420 goto error;
2421 }
2422
Willy Tarreau51cd5952020-06-05 12:25:38 +02002423 cur_arg++;
2424 }
2425 else if (strcmp(args[cur_arg], "port") == 0) {
2426 const char *p, *end;
2427
2428 if (!*(args[cur_arg+1])) {
2429 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2430 goto error;
2431 }
2432 cur_arg++;
2433
2434 port = 0;
2435 release_sample_expr(port_expr);
2436 p = args[cur_arg]; end = p + strlen(p);
2437 port = read_uint(&p, end);
2438 if (p != end) {
2439 int idx = 0;
2440
2441 px->conf.args.ctx = ARGC_SRV;
2442 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002443 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002444
2445 if (!port_expr) {
2446 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2447 goto error;
2448 }
2449 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2450 memprintf(errmsg, "error detected while parsing port expression : "
2451 " fetch method '%s' extracts information from '%s', "
2452 "none of which is available here.\n",
2453 args[cur_arg], sample_src_names(port_expr->fetch->use));
2454 goto error;
2455 }
2456 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2457 }
2458 else if (port > 65535 || port < 1) {
2459 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2460 args[cur_arg]);
2461 goto error;
2462 }
2463 }
2464 else if (strcmp(args[cur_arg], "proto") == 0) {
2465 if (!*(args[cur_arg+1])) {
2466 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2467 goto error;
2468 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002469 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002470 if (!mux_proto) {
2471 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2472 goto error;
2473 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002474
2475 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2476 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2477 goto error;
2478 }
2479 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2480 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2481 goto error;
2482 }
2483
Willy Tarreau51cd5952020-06-05 12:25:38 +02002484 cur_arg++;
2485 }
2486 else if (strcmp(args[cur_arg], "comment") == 0) {
2487 if (!*(args[cur_arg+1])) {
2488 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2489 goto error;
2490 }
2491 cur_arg++;
2492 free(comment);
2493 comment = strdup(args[cur_arg]);
2494 if (!comment) {
2495 memprintf(errmsg, "out of memory");
2496 goto error;
2497 }
2498 }
2499 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2500 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2501 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2502 conn_opts |= TCPCHK_OPT_SOCKS4;
2503 else if (strcmp(args[cur_arg], "linger") == 0)
2504 conn_opts |= TCPCHK_OPT_LINGER;
2505#ifdef USE_OPENSSL
2506 else if (strcmp(args[cur_arg], "ssl") == 0) {
2507 px->options |= PR_O_TCPCHK_SSL;
2508 conn_opts |= TCPCHK_OPT_SSL;
2509 }
2510 else if (strcmp(args[cur_arg], "sni") == 0) {
2511 if (!*(args[cur_arg+1])) {
2512 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2513 goto error;
2514 }
2515 cur_arg++;
2516 free(sni);
2517 sni = strdup(args[cur_arg]);
2518 if (!sni) {
2519 memprintf(errmsg, "out of memory");
2520 goto error;
2521 }
2522 }
2523 else if (strcmp(args[cur_arg], "alpn") == 0) {
2524#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2525 free(alpn);
2526 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2527 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2528 goto error;
2529 }
2530 cur_arg++;
2531#else
2532 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2533 goto error;
2534#endif
2535 }
2536#endif /* USE_OPENSSL */
2537
2538 else {
2539 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2540#ifdef USE_OPENSSL
2541 ", 'ssl', 'sni', 'alpn'"
2542#endif /* USE_OPENSSL */
2543 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2544 args[cur_arg]);
2545 goto error;
2546 }
2547 cur_arg++;
2548 }
2549
2550 chk = calloc(1, sizeof(*chk));
2551 if (!chk) {
2552 memprintf(errmsg, "out of memory");
2553 goto error;
2554 }
2555 chk->action = TCPCHK_ACT_CONNECT;
2556 chk->comment = comment;
2557 chk->connect.port = port;
2558 chk->connect.options = conn_opts;
2559 chk->connect.sni = sni;
2560 chk->connect.alpn = alpn;
2561 chk->connect.alpn_len= alpn_len;
2562 chk->connect.port_expr= port_expr;
2563 chk->connect.mux_proto= mux_proto;
2564 if (sk)
2565 chk->connect.addr = *sk;
2566 return chk;
2567
2568 error:
2569 free(alpn);
2570 free(sni);
2571 free(comment);
2572 release_sample_expr(port_expr);
2573 return NULL;
2574}
2575
2576/* Parses and creates a tcp-check send rule. NULL is returned on error */
2577struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2578 const char *file, int line, char **errmsg)
2579{
2580 struct tcpcheck_rule *chk = NULL;
2581 char *comment = NULL, *data = NULL;
2582 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2583
2584 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2585 type = TCPCHK_SEND_BINARY_LF;
2586 else if (strcmp(args[cur_arg], "send-binary") == 0)
2587 type = TCPCHK_SEND_BINARY;
2588 else if (strcmp(args[cur_arg], "send-lf") == 0)
2589 type = TCPCHK_SEND_STRING_LF;
2590 else if (strcmp(args[cur_arg], "send") == 0)
2591 type = TCPCHK_SEND_STRING;
2592
2593 if (!*(args[cur_arg+1])) {
2594 memprintf(errmsg, "'%s' expects a %s as argument",
2595 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2596 goto error;
2597 }
2598
2599 data = args[cur_arg+1];
2600
2601 cur_arg += 2;
2602 while (*(args[cur_arg])) {
2603 if (strcmp(args[cur_arg], "comment") == 0) {
2604 if (!*(args[cur_arg+1])) {
2605 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2606 goto error;
2607 }
2608 cur_arg++;
2609 free(comment);
2610 comment = strdup(args[cur_arg]);
2611 if (!comment) {
2612 memprintf(errmsg, "out of memory");
2613 goto error;
2614 }
2615 }
2616 else {
2617 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2618 args[cur_arg]);
2619 goto error;
2620 }
2621 cur_arg++;
2622 }
2623
2624 chk = calloc(1, sizeof(*chk));
2625 if (!chk) {
2626 memprintf(errmsg, "out of memory");
2627 goto error;
2628 }
2629 chk->action = TCPCHK_ACT_SEND;
2630 chk->comment = comment;
2631 chk->send.type = type;
2632
2633 switch (chk->send.type) {
2634 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002635 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002636 if (!isttest(chk->send.data)) {
2637 memprintf(errmsg, "out of memory");
2638 goto error;
2639 }
2640 break;
2641 case TCPCHK_SEND_BINARY: {
2642 int len = chk->send.data.len;
2643 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2644 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2645 goto error;
2646 }
2647 chk->send.data.len = len;
2648 break;
2649 }
2650 case TCPCHK_SEND_STRING_LF:
2651 case TCPCHK_SEND_BINARY_LF:
2652 LIST_INIT(&chk->send.fmt);
2653 px->conf.args.ctx = ARGC_SRV;
2654 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2655 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2656 goto error;
2657 }
2658 break;
2659 case TCPCHK_SEND_HTTP:
2660 case TCPCHK_SEND_UNDEF:
2661 goto error;
2662 }
2663
2664 return chk;
2665
2666 error:
2667 free(chk);
2668 free(comment);
2669 return NULL;
2670}
2671
2672/* Parses and creates a http-check send rule. NULL is returned on error */
2673struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2674 const char *file, int line, char **errmsg)
2675{
2676 struct tcpcheck_rule *chk = NULL;
2677 struct tcpcheck_http_hdr *hdr = NULL;
2678 struct http_hdr hdrs[global.tune.max_http_hdr];
2679 char *meth = NULL, *uri = NULL, *vsn = NULL;
2680 char *body = NULL, *comment = NULL;
2681 unsigned int flags = 0;
2682 int i = 0, host_hdr = -1;
2683
2684 cur_arg++;
2685 while (*(args[cur_arg])) {
2686 if (strcmp(args[cur_arg], "meth") == 0) {
2687 if (!*(args[cur_arg+1])) {
2688 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2689 goto error;
2690 }
2691 cur_arg++;
2692 meth = args[cur_arg];
2693 }
2694 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2695 if (!*(args[cur_arg+1])) {
2696 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2697 goto error;
2698 }
2699 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2700 if (strcmp(args[cur_arg], "uri-lf") == 0)
2701 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2702 cur_arg++;
2703 uri = args[cur_arg];
2704 }
2705 else if (strcmp(args[cur_arg], "ver") == 0) {
2706 if (!*(args[cur_arg+1])) {
2707 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2708 goto error;
2709 }
2710 cur_arg++;
2711 vsn = args[cur_arg];
2712 }
2713 else if (strcmp(args[cur_arg], "hdr") == 0) {
2714 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2715 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2716 goto error;
2717 }
2718
2719 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2720 if (host_hdr >= 0) {
2721 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2722 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2723 goto error;
2724 }
2725 host_hdr = i;
2726 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002727 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002728 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2729 goto skip_hdr;
2730
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002731 hdrs[i].n = ist(args[cur_arg + 1]);
2732 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002733 i++;
2734 skip_hdr:
2735 cur_arg += 2;
2736 }
2737 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2738 if (!*(args[cur_arg+1])) {
2739 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2740 goto error;
2741 }
2742 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2743 if (strcmp(args[cur_arg], "body-lf") == 0)
2744 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2745 cur_arg++;
2746 body = args[cur_arg];
2747 }
2748 else if (strcmp(args[cur_arg], "comment") == 0) {
2749 if (!*(args[cur_arg+1])) {
2750 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2751 goto error;
2752 }
2753 cur_arg++;
2754 free(comment);
2755 comment = strdup(args[cur_arg]);
2756 if (!comment) {
2757 memprintf(errmsg, "out of memory");
2758 goto error;
2759 }
2760 }
2761 else {
2762 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2763 " but got '%s' as argument.", args[cur_arg]);
2764 goto error;
2765 }
2766 cur_arg++;
2767 }
2768
2769 hdrs[i].n = hdrs[i].v = IST_NULL;
2770
2771 chk = calloc(1, sizeof(*chk));
2772 if (!chk) {
2773 memprintf(errmsg, "out of memory");
2774 goto error;
2775 }
2776 chk->action = TCPCHK_ACT_SEND;
2777 chk->comment = comment; comment = NULL;
2778 chk->send.type = TCPCHK_SEND_HTTP;
2779 chk->send.http.flags = flags;
2780 LIST_INIT(&chk->send.http.hdrs);
2781
2782 if (meth) {
2783 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2784 chk->send.http.meth.str.area = strdup(meth);
2785 chk->send.http.meth.str.data = strlen(meth);
2786 if (!chk->send.http.meth.str.area) {
2787 memprintf(errmsg, "out of memory");
2788 goto error;
2789 }
2790 }
2791 if (uri) {
2792 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2793 LIST_INIT(&chk->send.http.uri_fmt);
2794 px->conf.args.ctx = ARGC_SRV;
2795 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2796 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2797 goto error;
2798 }
2799 }
2800 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002801 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002802 if (!isttest(chk->send.http.uri)) {
2803 memprintf(errmsg, "out of memory");
2804 goto error;
2805 }
2806 }
2807 }
2808 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002809 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002810 if (!isttest(chk->send.http.vsn)) {
2811 memprintf(errmsg, "out of memory");
2812 goto error;
2813 }
2814 }
2815 for (i = 0; istlen(hdrs[i].n); i++) {
2816 hdr = calloc(1, sizeof(*hdr));
2817 if (!hdr) {
2818 memprintf(errmsg, "out of memory");
2819 goto error;
2820 }
2821 LIST_INIT(&hdr->value);
2822 hdr->name = istdup(hdrs[i].n);
2823 if (!isttest(hdr->name)) {
2824 memprintf(errmsg, "out of memory");
2825 goto error;
2826 }
2827
2828 ist0(hdrs[i].v);
2829 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2830 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002831 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002832 hdr = NULL;
2833 }
2834
2835 if (body) {
2836 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2837 LIST_INIT(&chk->send.http.body_fmt);
2838 px->conf.args.ctx = ARGC_SRV;
2839 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2840 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2841 goto error;
2842 }
2843 }
2844 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002845 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002846 if (!isttest(chk->send.http.body)) {
2847 memprintf(errmsg, "out of memory");
2848 goto error;
2849 }
2850 }
2851 }
2852
2853 return chk;
2854
2855 error:
2856 free_tcpcheck_http_hdr(hdr);
2857 free_tcpcheck(chk, 0);
2858 free(comment);
2859 return NULL;
2860}
2861
2862/* Parses and creates a http-check comment rule. NULL is returned on error */
2863struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2864 const char *file, int line, char **errmsg)
2865{
2866 struct tcpcheck_rule *chk = NULL;
2867 char *comment = NULL;
2868
2869 if (!*(args[cur_arg+1])) {
2870 memprintf(errmsg, "expects a string as argument");
2871 goto error;
2872 }
2873 cur_arg++;
2874 comment = strdup(args[cur_arg]);
2875 if (!comment) {
2876 memprintf(errmsg, "out of memory");
2877 goto error;
2878 }
2879
2880 chk = calloc(1, sizeof(*chk));
2881 if (!chk) {
2882 memprintf(errmsg, "out of memory");
2883 goto error;
2884 }
2885 chk->action = TCPCHK_ACT_COMMENT;
2886 chk->comment = comment;
2887 return chk;
2888
2889 error:
2890 free(comment);
2891 return NULL;
2892}
2893
2894/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2895 * on error. <proto> is set to the right protocol flags (covered by the
2896 * TCPCHK_RULES_PROTO_CHK mask).
2897 */
2898struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2899 struct list *rules, unsigned int proto,
2900 const char *file, int line, char **errmsg)
2901{
2902 struct tcpcheck_rule *prev_check, *chk = NULL;
2903 struct sample_expr *status_expr = NULL;
2904 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2905 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2906 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2907 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2908 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2909 unsigned int flags = 0;
2910 long min_recv = -1;
2911 int inverse = 0;
2912
2913 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2914 if (!*(args[cur_arg+1])) {
2915 memprintf(errmsg, "expects at least a matching pattern as arguments");
2916 goto error;
2917 }
2918
2919 cur_arg++;
2920 while (*(args[cur_arg])) {
2921 int in_pattern = 0;
2922
2923 rescan:
2924 if (strcmp(args[cur_arg], "min-recv") == 0) {
2925 if (in_pattern) {
2926 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2927 goto error;
2928 }
2929 if (!*(args[cur_arg+1])) {
2930 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2931 goto error;
2932 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002933 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002934 cur_arg++;
2935 min_recv = atol(args[cur_arg]);
2936 if (min_recv < -1 || min_recv > INT_MAX) {
2937 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2938 goto error;
2939 }
2940 }
2941 else if (*(args[cur_arg]) == '!') {
2942 in_pattern = 1;
2943 while (*(args[cur_arg]) == '!') {
2944 inverse = !inverse;
2945 args[cur_arg]++;
2946 }
2947 if (!*(args[cur_arg]))
2948 cur_arg++;
2949 goto rescan;
2950 }
2951 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2952 if (type != TCPCHK_EXPECT_UNDEF) {
2953 memprintf(errmsg, "only on pattern expected");
2954 goto error;
2955 }
2956 if (proto != TCPCHK_RULES_HTTP_CHK)
2957 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2958 else
2959 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2960
2961 if (!*(args[cur_arg+1])) {
2962 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2963 goto error;
2964 }
2965 cur_arg++;
2966 pattern = args[cur_arg];
2967 }
2968 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2969 if (proto == TCPCHK_RULES_HTTP_CHK)
2970 goto bad_http_kw;
2971 if (type != TCPCHK_EXPECT_UNDEF) {
2972 memprintf(errmsg, "only on pattern expected");
2973 goto error;
2974 }
2975 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2976
2977 if (!*(args[cur_arg+1])) {
2978 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2979 goto error;
2980 }
2981 cur_arg++;
2982 pattern = args[cur_arg];
2983 }
2984 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2985 if (type != TCPCHK_EXPECT_UNDEF) {
2986 memprintf(errmsg, "only on pattern expected");
2987 goto error;
2988 }
2989 if (proto != TCPCHK_RULES_HTTP_CHK)
2990 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2991 else {
2992 if (*(args[cur_arg]) != 's')
2993 goto bad_http_kw;
2994 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2995 }
2996
2997 if (!*(args[cur_arg+1])) {
2998 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2999 goto error;
3000 }
3001 cur_arg++;
3002 pattern = args[cur_arg];
3003 }
3004 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3005 if (proto != TCPCHK_RULES_HTTP_CHK)
3006 goto bad_tcp_kw;
3007 if (type != TCPCHK_EXPECT_UNDEF) {
3008 memprintf(errmsg, "only on pattern expected");
3009 goto error;
3010 }
3011 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3012
3013 if (!*(args[cur_arg+1])) {
3014 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3015 goto error;
3016 }
3017 cur_arg++;
3018 pattern = args[cur_arg];
3019 }
3020 else if (strcmp(args[cur_arg], "custom") == 0) {
3021 if (in_pattern) {
3022 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3023 goto error;
3024 }
3025 if (type != TCPCHK_EXPECT_UNDEF) {
3026 memprintf(errmsg, "only on pattern expected");
3027 goto error;
3028 }
3029 type = TCPCHK_EXPECT_CUSTOM;
3030 }
3031 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3032 int orig_arg = cur_arg;
3033
3034 if (proto != TCPCHK_RULES_HTTP_CHK)
3035 goto bad_tcp_kw;
3036 if (type != TCPCHK_EXPECT_UNDEF) {
3037 memprintf(errmsg, "only on pattern expected");
3038 goto error;
3039 }
3040 type = TCPCHK_EXPECT_HTTP_HEADER;
3041
3042 if (strcmp(args[cur_arg], "fhdr") == 0)
3043 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3044
3045 /* Parse the name pattern, mandatory */
3046 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3047 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3048 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3049 args[orig_arg]);
3050 goto error;
3051 }
3052
3053 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3054 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3055
3056 cur_arg += 2;
3057 if (strcmp(args[cur_arg], "-m") == 0) {
3058 if (!*(args[cur_arg+1])) {
3059 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3060 args[orig_arg], args[cur_arg]);
3061 goto error;
3062 }
3063 if (strcmp(args[cur_arg+1], "str") == 0)
3064 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3065 else if (strcmp(args[cur_arg+1], "beg") == 0)
3066 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3067 else if (strcmp(args[cur_arg+1], "end") == 0)
3068 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3069 else if (strcmp(args[cur_arg+1], "sub") == 0)
3070 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3071 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3072 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3073 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3074 args[orig_arg]);
3075 goto error;
3076 }
3077 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3078 }
3079 else {
3080 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3081 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3082 goto error;
3083 }
3084 cur_arg += 2;
3085 }
3086 else
3087 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3088 npat = args[cur_arg];
3089
3090 if (!*(args[cur_arg+1]) ||
3091 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3092 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3093 goto next;
3094 }
3095 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3096 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3097
3098 /* Parse the value pattern, optional */
3099 if (strcmp(args[cur_arg+2], "-m") == 0) {
3100 cur_arg += 2;
3101 if (!*(args[cur_arg+1])) {
3102 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3103 args[orig_arg], args[cur_arg]);
3104 goto error;
3105 }
3106 if (strcmp(args[cur_arg+1], "str") == 0)
3107 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3108 else if (strcmp(args[cur_arg+1], "beg") == 0)
3109 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3110 else if (strcmp(args[cur_arg+1], "end") == 0)
3111 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3112 else if (strcmp(args[cur_arg+1], "sub") == 0)
3113 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3114 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3115 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3116 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3117 args[orig_arg]);
3118 goto error;
3119 }
3120 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3121 }
3122 else {
3123 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3124 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3125 goto error;
3126 }
3127 }
3128 else
3129 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3130
3131 if (!*(args[cur_arg+2])) {
3132 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3133 goto error;
3134 }
3135 vpat = args[cur_arg+2];
3136 cur_arg += 2;
3137 }
3138 else if (strcmp(args[cur_arg], "comment") == 0) {
3139 if (in_pattern) {
3140 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3141 goto error;
3142 }
3143 if (!*(args[cur_arg+1])) {
3144 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3145 goto error;
3146 }
3147 cur_arg++;
3148 free(comment);
3149 comment = strdup(args[cur_arg]);
3150 if (!comment) {
3151 memprintf(errmsg, "out of memory");
3152 goto error;
3153 }
3154 }
3155 else if (strcmp(args[cur_arg], "on-success") == 0) {
3156 if (in_pattern) {
3157 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3158 goto error;
3159 }
3160 if (!*(args[cur_arg+1])) {
3161 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3162 goto error;
3163 }
3164 cur_arg++;
3165 on_success_msg = args[cur_arg];
3166 }
3167 else if (strcmp(args[cur_arg], "on-error") == 0) {
3168 if (in_pattern) {
3169 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3170 goto error;
3171 }
3172 if (!*(args[cur_arg+1])) {
3173 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3174 goto error;
3175 }
3176 cur_arg++;
3177 on_error_msg = args[cur_arg];
3178 }
3179 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3180 if (in_pattern) {
3181 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3182 goto error;
3183 }
3184 if (!*(args[cur_arg+1])) {
3185 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3186 goto error;
3187 }
3188 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3189 ok_st = HCHK_STATUS_L7OKD;
3190 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3191 ok_st = HCHK_STATUS_L7OKCD;
3192 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3193 ok_st = HCHK_STATUS_L6OK;
3194 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3195 ok_st = HCHK_STATUS_L4OK;
3196 else {
3197 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3198 args[cur_arg], args[cur_arg+1]);
3199 goto error;
3200 }
3201 cur_arg++;
3202 }
3203 else if (strcmp(args[cur_arg], "error-status") == 0) {
3204 if (in_pattern) {
3205 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3206 goto error;
3207 }
3208 if (!*(args[cur_arg+1])) {
3209 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3210 goto error;
3211 }
3212 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3213 err_st = HCHK_STATUS_L7RSP;
3214 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3215 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003216 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3217 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003218 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3219 err_st = HCHK_STATUS_L6RSP;
3220 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3221 err_st = HCHK_STATUS_L4CON;
3222 else {
3223 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3224 args[cur_arg], args[cur_arg+1]);
3225 goto error;
3226 }
3227 cur_arg++;
3228 }
3229 else if (strcmp(args[cur_arg], "status-code") == 0) {
3230 int idx = 0;
3231
3232 if (in_pattern) {
3233 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3234 goto error;
3235 }
3236 if (!*(args[cur_arg+1])) {
3237 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3238 goto error;
3239 }
3240
3241 cur_arg++;
3242 release_sample_expr(status_expr);
3243 px->conf.args.ctx = ARGC_SRV;
3244 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003245 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003246 if (!status_expr) {
3247 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3248 goto error;
3249 }
3250 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3251 memprintf(errmsg, "error detected while parsing status-code expression : "
3252 " fetch method '%s' extracts information from '%s', "
3253 "none of which is available here.\n",
3254 args[cur_arg], sample_src_names(status_expr->fetch->use));
3255 goto error;
3256 }
3257 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3258 }
3259 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3260 if (in_pattern) {
3261 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3262 goto error;
3263 }
3264 if (!*(args[cur_arg+1])) {
3265 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3266 goto error;
3267 }
3268 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3269 tout_st = HCHK_STATUS_L7TOUT;
3270 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3271 tout_st = HCHK_STATUS_L6TOUT;
3272 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3273 tout_st = HCHK_STATUS_L4TOUT;
3274 else {
3275 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3276 args[cur_arg], args[cur_arg+1]);
3277 goto error;
3278 }
3279 cur_arg++;
3280 }
3281 else {
3282 if (proto == TCPCHK_RULES_HTTP_CHK) {
3283 bad_http_kw:
3284 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3285 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3286 }
3287 else {
3288 bad_tcp_kw:
3289 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3290 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3291 }
3292 goto error;
3293 }
3294 next:
3295 cur_arg++;
3296 }
3297
3298 chk = calloc(1, sizeof(*chk));
3299 if (!chk) {
3300 memprintf(errmsg, "out of memory");
3301 goto error;
3302 }
3303 chk->action = TCPCHK_ACT_EXPECT;
3304 LIST_INIT(&chk->expect.onerror_fmt);
3305 LIST_INIT(&chk->expect.onsuccess_fmt);
3306 chk->comment = comment; comment = NULL;
3307 chk->expect.type = type;
3308 chk->expect.min_recv = min_recv;
3309 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3310 chk->expect.ok_status = ok_st;
3311 chk->expect.err_status = err_st;
3312 chk->expect.tout_status = tout_st;
3313 chk->expect.status_expr = status_expr; status_expr = NULL;
3314
3315 if (on_success_msg) {
3316 px->conf.args.ctx = ARGC_SRV;
3317 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3318 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3319 goto error;
3320 }
3321 }
3322 if (on_error_msg) {
3323 px->conf.args.ctx = ARGC_SRV;
3324 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3325 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3326 goto error;
3327 }
3328 }
3329
3330 switch (chk->expect.type) {
3331 case TCPCHK_EXPECT_HTTP_STATUS: {
3332 const char *p = pattern;
3333 unsigned int c1,c2;
3334
3335 chk->expect.codes.codes = NULL;
3336 chk->expect.codes.num = 0;
3337 while (1) {
3338 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3339 if (*p == '-') {
3340 p++;
3341 c2 = read_uint(&p, pattern + strlen(pattern));
3342 }
3343 if (c1 > c2) {
3344 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3345 goto error;
3346 }
3347
3348 chk->expect.codes.num++;
3349 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3350 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3351 if (!chk->expect.codes.codes) {
3352 memprintf(errmsg, "out of memory");
3353 goto error;
3354 }
3355 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3356 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3357
3358 if (*p == '\0')
3359 break;
3360 if (*p != ',') {
3361 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3362 goto error;
3363 }
3364 p++;
3365 }
3366 break;
3367 }
3368 case TCPCHK_EXPECT_STRING:
3369 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003370 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003371 if (!isttest(chk->expect.data)) {
3372 memprintf(errmsg, "out of memory");
3373 goto error;
3374 }
3375 break;
3376 case TCPCHK_EXPECT_BINARY: {
3377 int len = chk->expect.data.len;
3378
3379 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3380 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3381 goto error;
3382 }
3383 chk->expect.data.len = len;
3384 break;
3385 }
3386 case TCPCHK_EXPECT_STRING_REGEX:
3387 case TCPCHK_EXPECT_BINARY_REGEX:
3388 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3389 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3390 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3391 if (!chk->expect.regex)
3392 goto error;
3393 break;
3394
3395 case TCPCHK_EXPECT_STRING_LF:
3396 case TCPCHK_EXPECT_BINARY_LF:
3397 case TCPCHK_EXPECT_HTTP_BODY_LF:
3398 LIST_INIT(&chk->expect.fmt);
3399 px->conf.args.ctx = ARGC_SRV;
3400 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3401 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3402 goto error;
3403 }
3404 break;
3405
3406 case TCPCHK_EXPECT_HTTP_HEADER:
3407 if (!npat) {
3408 memprintf(errmsg, "unexpected error, undefined header name pattern");
3409 goto error;
3410 }
3411 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3412 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3413 if (!chk->expect.hdr.name_re)
3414 goto error;
3415 }
3416 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3417 px->conf.args.ctx = ARGC_SRV;
3418 LIST_INIT(&chk->expect.hdr.name_fmt);
3419 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3420 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3421 goto error;
3422 }
3423 }
3424 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003425 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003426 if (!isttest(chk->expect.hdr.name)) {
3427 memprintf(errmsg, "out of memory");
3428 goto error;
3429 }
3430 }
3431
3432 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3433 chk->expect.hdr.value = IST_NULL;
3434 break;
3435 }
3436
3437 if (!vpat) {
3438 memprintf(errmsg, "unexpected error, undefined header value pattern");
3439 goto error;
3440 }
3441 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3442 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3443 if (!chk->expect.hdr.value_re)
3444 goto error;
3445 }
3446 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3447 px->conf.args.ctx = ARGC_SRV;
3448 LIST_INIT(&chk->expect.hdr.value_fmt);
3449 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
Willy Tarreaub14c8e12024-05-31 18:37:56 +02003450 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", vpat, *errmsg);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003451 goto error;
3452 }
3453 }
3454 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003455 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003456 if (!isttest(chk->expect.hdr.value)) {
3457 memprintf(errmsg, "out of memory");
3458 goto error;
3459 }
3460 }
3461
3462 break;
3463 case TCPCHK_EXPECT_CUSTOM:
3464 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3465 break;
3466 case TCPCHK_EXPECT_UNDEF:
3467 memprintf(errmsg, "pattern not found");
3468 goto error;
3469 }
3470
3471 /* All tcp-check expect points back to the first inverse expect rule in
3472 * a chain of one or more expect rule, potentially itself.
3473 */
3474 chk->expect.head = chk;
3475 list_for_each_entry_rev(prev_check, rules, list) {
3476 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3477 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3478 chk->expect.head = prev_check;
3479 continue;
3480 }
3481 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3482 break;
3483 }
3484 return chk;
3485
3486 error:
3487 free_tcpcheck(chk, 0);
3488 free(comment);
3489 release_sample_expr(status_expr);
3490 return NULL;
3491}
3492
3493/* Overwrites fields of the old http send rule with those of the new one. When
3494 * replaced, old values are freed and replaced by the new ones. New values are
3495 * not copied but transferred. At the end <new> should be empty and can be
3496 * safely released. This function never fails.
3497 */
3498void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3499{
3500 struct logformat_node *lf, *lfb;
3501 struct tcpcheck_http_hdr *hdr, *bhdr;
3502
3503
3504 if (new->send.http.meth.str.area) {
3505 free(old->send.http.meth.str.area);
3506 old->send.http.meth.meth = new->send.http.meth.meth;
3507 old->send.http.meth.str.area = new->send.http.meth.str.area;
3508 old->send.http.meth.str.data = new->send.http.meth.str.data;
3509 new->send.http.meth.str = BUF_NULL;
3510 }
3511
3512 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3513 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3514 istfree(&old->send.http.uri);
3515 else
3516 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3517 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3518 old->send.http.uri = new->send.http.uri;
3519 new->send.http.uri = IST_NULL;
3520 }
3521 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3522 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3523 istfree(&old->send.http.uri);
3524 else
3525 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3526 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3527 LIST_INIT(&old->send.http.uri_fmt);
3528 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003529 LIST_DELETE(&lf->list);
3530 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003531 }
3532 }
3533
3534 if (isttest(new->send.http.vsn)) {
3535 istfree(&old->send.http.vsn);
3536 old->send.http.vsn = new->send.http.vsn;
3537 new->send.http.vsn = IST_NULL;
3538 }
3539
Christopher Faulet4c8e58d2022-07-05 15:33:53 +02003540 if (!LIST_ISEMPTY(&new->send.http.hdrs)) {
3541 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3542 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
3543 LIST_DELETE(&hdr->list);
3544 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
3545 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02003546 }
3547
3548 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3549 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3550 istfree(&old->send.http.body);
3551 else
3552 free_tcpcheck_fmt(&old->send.http.body_fmt);
3553 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3554 old->send.http.body = new->send.http.body;
3555 new->send.http.body = IST_NULL;
3556 }
3557 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3558 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3559 istfree(&old->send.http.body);
3560 else
3561 free_tcpcheck_fmt(&old->send.http.body_fmt);
3562 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3563 LIST_INIT(&old->send.http.body_fmt);
3564 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003565 LIST_DELETE(&lf->list);
3566 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003567 }
3568 }
3569}
3570
3571/* Internal function used to add an http-check rule in a list during the config
3572 * parsing step. Depending on its type, and the previously inserted rules, a
3573 * specific action may be performed or an error may be reported. This functions
3574 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3575 * message.
3576 */
3577int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3578{
3579 struct tcpcheck_rule *r;
3580
3581 /* the implicit send rule coming from an "option httpchk" line must be
3582 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003583 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003584 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003585 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003586 * sure the ruleset remains valid.
3587 */
3588
3589 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3590 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3591 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3592 * following tests are performed :
3593 *
3594 * 1- If there is no such rule or if it is not a send rule, the implicit send
3595 * rule is pushed in front of the ruleset
3596 *
3597 * 2- If it is another implicit send rule, it is replaced with the new one.
3598 *
3599 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3600 * both, overwriting the old send rule (the explicit one) with info of the
3601 * new send rule (the implicit one).
3602 */
3603 r = get_first_tcpcheck_rule(rules);
3604 if (r && r->action == TCPCHK_ACT_CONNECT)
3605 r = get_next_tcpcheck_rule(rules, r);
3606 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003607 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003608 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003609 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003610 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003611 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003612 }
3613 else {
3614 tcpcheck_overwrite_send_http_rule(r, chk);
3615 free_tcpcheck(chk, 0);
3616 }
3617 }
3618 else {
3619 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3620 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3621 * with an existing implicit send rule, if any. At the end, if there is no error,
3622 * the rule is appended to the list.
3623 */
3624
3625 r = get_last_tcpcheck_rule(rules);
3626 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3627 /* no error */;
3628 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3629 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3630 chk->index+1);
3631 return 0;
3632 }
3633 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3634 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3635 chk->index+1);
3636 return 0;
3637 }
3638 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3639 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3640 chk->index+1);
3641 return 0;
3642 }
3643
3644 if (chk->action == TCPCHK_ACT_SEND) {
3645 r = get_first_tcpcheck_rule(rules);
3646 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3647 tcpcheck_overwrite_send_http_rule(r, chk);
3648 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003649 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003650 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3651 chk = r;
3652 }
3653 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003654 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003655 }
3656 return 1;
3657}
3658
3659/* Check tcp-check health-check configuration for the proxy <px>. */
3660static int check_proxy_tcpcheck(struct proxy *px)
3661{
3662 struct tcpcheck_rule *chk, *back;
3663 char *comment = NULL, *errmsg = NULL;
3664 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003665 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003666
3667 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3668 deinit_proxy_tcpcheck(px);
3669 goto out;
3670 }
3671
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003672 ha_free(&px->check_command);
3673 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003674
3675 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003676 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003677 ret |= ERR_ALERT | ERR_FATAL;
3678 goto out;
3679 }
3680
3681 /* HTTP ruleset only : */
3682 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3683 struct tcpcheck_rule *next;
3684
3685 /* move remaining implicit send rule from "option httpchk" line to the right place.
3686 * If such rule exists, it must be the first one. In this case, the rule is moved
3687 * after the first connect rule, if any. Otherwise, nothing is done.
3688 */
3689 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3690 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3691 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3692 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003693 LIST_DELETE(&chk->list);
3694 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003695 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003696 }
3697 }
3698
3699 /* add implicit expect rule if the last one is a send. It is inherited from previous
3700 * versions where the http expect rule was optional. Now it is possible to chained
3701 * send/expect rules but the last expect may still be implicit.
3702 */
3703 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3704 if (chk && chk->action == TCPCHK_ACT_SEND) {
3705 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3706 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3707 px->conf.file, px->conf.line, &errmsg);
3708 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003709 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003710 "(%s).\n", px->id, errmsg);
3711 free(errmsg);
3712 ret |= ERR_ALERT | ERR_FATAL;
3713 goto out;
3714 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003715 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003716 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003717 }
3718 }
3719
3720 /* For all ruleset: */
3721
3722 /* If there is no connect rule preceding all send / expect rules, an
3723 * implicit one is inserted before all others.
3724 */
3725 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3726 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3727 chk = calloc(1, sizeof(*chk));
3728 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003729 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003730 "(out of memory).\n", px->id);
3731 ret |= ERR_ALERT | ERR_FATAL;
3732 goto out;
3733 }
3734 chk->action = TCPCHK_ACT_CONNECT;
3735 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003736 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003737 }
3738
3739 /* Remove all comment rules. To do so, when a such rule is found, the
3740 * comment is assigned to the following rule(s).
3741 */
3742 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Faulet871dd822022-08-24 11:38:03 +02003743 struct tcpcheck_rule *next;
3744
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003745 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3746 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003747
3748 prev_action = chk->action;
3749 switch (chk->action) {
3750 case TCPCHK_ACT_COMMENT:
3751 free(comment);
3752 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003753 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003754 free(chk);
3755 break;
3756 case TCPCHK_ACT_CONNECT:
3757 if (!chk->comment && comment)
3758 chk->comment = strdup(comment);
Christopher Faulet871dd822022-08-24 11:38:03 +02003759 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3760 if (next && next->action == TCPCHK_ACT_SEND)
3761 chk->connect.options |= TCPCHK_OPT_HAS_DATA;
Willy Tarreauf3f60762022-11-14 07:10:33 +01003762 __fallthrough;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003763 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003764 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003765 break;
3766 case TCPCHK_ACT_SEND:
3767 case TCPCHK_ACT_EXPECT:
3768 if (!chk->comment && comment)
3769 chk->comment = strdup(comment);
3770 break;
3771 }
3772 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003773 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003774
3775 out:
3776 return ret;
3777}
3778
3779void deinit_proxy_tcpcheck(struct proxy *px)
3780{
3781 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3782 px->tcpcheck_rules.flags = 0;
3783 px->tcpcheck_rules.list = NULL;
3784}
3785
3786static void deinit_tcpchecks()
3787{
3788 struct tcpcheck_ruleset *rs;
3789 struct tcpcheck_rule *r, *rb;
3790 struct ebpt_node *node, *next;
3791
3792 node = ebpt_first(&shared_tcpchecks);
3793 while (node) {
3794 next = ebpt_next(node);
3795 ebpt_delete(node);
3796 free(node->key);
3797 rs = container_of(node, typeof(*rs), node);
3798 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003799 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003800 free_tcpcheck(r, 0);
3801 }
3802 free(rs);
3803 node = next;
3804 }
3805}
3806
3807int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3808{
3809 struct tcpcheck_rule *tcpcheck, *prev_check;
3810 struct tcpcheck_expect *expect;
3811
Willy Tarreau6922e552021-03-22 21:11:45 +01003812 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003813 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003814 tcpcheck->action = TCPCHK_ACT_EXPECT;
3815
3816 expect = &tcpcheck->expect;
3817 expect->type = TCPCHK_EXPECT_STRING;
3818 LIST_INIT(&expect->onerror_fmt);
3819 LIST_INIT(&expect->onsuccess_fmt);
3820 expect->ok_status = HCHK_STATUS_L7OKD;
3821 expect->err_status = HCHK_STATUS_L7RSP;
3822 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003823 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003824 if (!isttest(expect->data)) {
3825 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3826 return 0;
3827 }
3828
3829 /* All tcp-check expect points back to the first inverse expect rule
3830 * in a chain of one or more expect rule, potentially itself.
3831 */
3832 tcpcheck->expect.head = tcpcheck;
3833 list_for_each_entry_rev(prev_check, rules->list, list) {
3834 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3835 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3836 tcpcheck->expect.head = prev_check;
3837 continue;
3838 }
3839 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3840 break;
3841 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003842 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003843 return 1;
3844}
3845
3846int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3847{
3848 struct tcpcheck_rule *tcpcheck;
3849 struct tcpcheck_send *send;
3850 const char *in;
3851 char *dst;
3852 int i;
3853
Willy Tarreau6922e552021-03-22 21:11:45 +01003854 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003855 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003856 tcpcheck->action = TCPCHK_ACT_SEND;
3857
3858 send = &tcpcheck->send;
3859 send->type = TCPCHK_SEND_STRING;
3860
3861 for (i = 0; strs[i]; i++)
3862 send->data.len += strlen(strs[i]);
3863
3864 send->data.ptr = malloc(istlen(send->data) + 1);
3865 if (!isttest(send->data)) {
3866 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3867 return 0;
3868 }
3869
3870 dst = istptr(send->data);
3871 for (i = 0; strs[i]; i++)
3872 for (in = strs[i]; (*dst = *in++); dst++);
3873 *dst = 0;
3874
Willy Tarreau2b718102021-04-21 07:32:39 +02003875 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003876 return 1;
3877}
3878
3879/* Parses the "tcp-check" proxy keyword */
3880static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003881 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003882 char **errmsg)
3883{
3884 struct tcpcheck_ruleset *rs = NULL;
3885 struct tcpcheck_rule *chk = NULL;
3886 int index, cur_arg, ret = 0;
3887
3888 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3889 ret = 1;
3890
3891 /* Deduce the ruleset name from the proxy info */
3892 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3893 ((curpx == defpx) ? "defaults" : curpx->id),
3894 curpx->conf.file, curpx->conf.line);
3895
3896 rs = find_tcpcheck_ruleset(b_orig(&trash));
3897 if (rs == NULL) {
3898 rs = create_tcpcheck_ruleset(b_orig(&trash));
3899 if (rs == NULL) {
3900 memprintf(errmsg, "out of memory.\n");
3901 goto error;
3902 }
3903 }
3904
3905 index = 0;
3906 if (!LIST_ISEMPTY(&rs->rules)) {
3907 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3908 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003909 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003910 }
3911
3912 cur_arg = 1;
3913 if (strcmp(args[cur_arg], "connect") == 0)
3914 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3915 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3916 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3917 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3918 else if (strcmp(args[cur_arg], "expect") == 0)
3919 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3920 else if (strcmp(args[cur_arg], "comment") == 0)
3921 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3922 else {
3923 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3924
3925 if (!kw) {
3926 action_kw_tcp_check_build_list(&trash);
3927 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3928 "%s%s. but got '%s'",
3929 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3930 goto error;
3931 }
3932 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3933 }
3934
3935 if (!chk) {
3936 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3937 goto error;
3938 }
3939 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3940
3941 /* No error: add the tcp-check rule in the list */
3942 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003943 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003944
3945 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3946 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3947 /* Use this ruleset if the proxy already has tcp-check enabled */
3948 curpx->tcpcheck_rules.list = &rs->rules;
3949 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3950 }
3951 else {
3952 /* mark this ruleset as unused for now */
3953 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3954 }
3955
3956 return ret;
3957
3958 error:
3959 free_tcpcheck(chk, 0);
3960 free_tcpcheck_ruleset(rs);
3961 return -1;
3962}
3963
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003964/* Parses the "http-check" proxy keyword */
3965static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003966 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003967 char **errmsg)
3968{
3969 struct tcpcheck_ruleset *rs = NULL;
3970 struct tcpcheck_rule *chk = NULL;
3971 int index, cur_arg, ret = 0;
3972
3973 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3974 ret = 1;
3975
3976 cur_arg = 1;
3977 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3978 /* enable a graceful server shutdown on an HTTP 404 response */
3979 curpx->options |= PR_O_DISABLE404;
3980 if (too_many_args(1, args, errmsg, NULL))
3981 goto error;
3982 goto out;
3983 }
3984 else if (strcmp(args[cur_arg], "send-state") == 0) {
3985 /* enable emission of the apparent state of a server in HTTP checks */
3986 curpx->options2 |= PR_O2_CHK_SNDST;
3987 if (too_many_args(1, args, errmsg, NULL))
3988 goto error;
3989 goto out;
3990 }
3991
3992 /* Deduce the ruleset name from the proxy info */
3993 chunk_printf(&trash, "*http-check-%s_%s-%d",
3994 ((curpx == defpx) ? "defaults" : curpx->id),
3995 curpx->conf.file, curpx->conf.line);
3996
3997 rs = find_tcpcheck_ruleset(b_orig(&trash));
3998 if (rs == NULL) {
3999 rs = create_tcpcheck_ruleset(b_orig(&trash));
4000 if (rs == NULL) {
4001 memprintf(errmsg, "out of memory.\n");
4002 goto error;
4003 }
4004 }
4005
4006 index = 0;
4007 if (!LIST_ISEMPTY(&rs->rules)) {
4008 chk = LIST_PREV(&rs->rules, typeof(chk), list);
4009 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4010 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004011 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004012 }
4013
4014 if (strcmp(args[cur_arg], "connect") == 0)
4015 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4016 else if (strcmp(args[cur_arg], "send") == 0)
4017 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4018 else if (strcmp(args[cur_arg], "expect") == 0)
4019 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4020 file, line, errmsg);
4021 else if (strcmp(args[cur_arg], "comment") == 0)
4022 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4023 else {
4024 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4025
4026 if (!kw) {
4027 action_kw_tcp_check_build_list(&trash);
4028 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4029 " 'send', 'expect'%s%s. but got '%s'",
4030 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4031 goto error;
4032 }
4033 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4034 }
4035
4036 if (!chk) {
4037 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4038 goto error;
4039 }
4040 ret = (*errmsg != NULL); /* Handle warning */
4041
4042 chk->index = index;
4043 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4044 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4045 /* Use this ruleset if the proxy already has http-check enabled */
4046 curpx->tcpcheck_rules.list = &rs->rules;
4047 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4048 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4049 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4050 curpx->tcpcheck_rules.list = NULL;
4051 goto error;
4052 }
4053 }
4054 else {
4055 /* mark this ruleset as unused for now */
4056 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004057 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004058 }
4059
4060 out:
4061 return ret;
4062
4063 error:
4064 free_tcpcheck(chk, 0);
4065 free_tcpcheck_ruleset(rs);
4066 return -1;
4067}
4068
4069/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004070int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004071 const char *file, int line)
4072{
4073 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4074 static char *redis_res = "+PONG\r\n";
4075
4076 struct tcpcheck_ruleset *rs = NULL;
4077 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4078 struct tcpcheck_rule *chk;
4079 char *errmsg = NULL;
4080 int err_code = 0;
4081
4082 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4083 err_code |= ERR_WARN;
4084
4085 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4086 goto out;
4087
4088 curpx->options2 &= ~PR_O2_CHK_ANY;
4089 curpx->options2 |= PR_O2_TCPCHK_CHK;
4090
4091 free_tcpcheck_vars(&rules->preset_vars);
4092 rules->list = NULL;
4093 rules->flags = 0;
4094
4095 rs = find_tcpcheck_ruleset("*redis-check");
4096 if (rs)
4097 goto ruleset_found;
4098
4099 rs = create_tcpcheck_ruleset("*redis-check");
4100 if (rs == NULL) {
4101 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4102 goto error;
4103 }
4104
4105 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4106 1, curpx, &rs->rules, file, line, &errmsg);
4107 if (!chk) {
4108 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4109 goto error;
4110 }
4111 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004112 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004113
4114 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4115 "error-status", "L7STS",
4116 "on-error", "%[res.payload(0,0),cut_crlf]",
4117 "on-success", "Redis server is ok",
4118 ""},
4119 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4120 if (!chk) {
4121 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4122 goto error;
4123 }
4124 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004125 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004126
4127 ruleset_found:
4128 rules->list = &rs->rules;
4129 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4130 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4131
4132 out:
4133 free(errmsg);
4134 return err_code;
4135
4136 error:
4137 free_tcpcheck_ruleset(rs);
4138 err_code |= ERR_ALERT | ERR_FATAL;
4139 goto out;
4140}
4141
4142
4143/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004144int 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 +01004145 const char *file, int line)
4146{
4147 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4148 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4149 *
4150 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4151 */
4152 static char sslv3_client_hello[] = {
4153 "16" /* ContentType : 0x16 = Handshake */
4154 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4155 "0079" /* ContentLength : 0x79 bytes after this one */
4156 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4157 "000075" /* HandshakeLength : 0x75 bytes after this one */
4158 "0300" /* Hello Version : 0x0300 = v3 */
4159 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4160 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4161 "00" /* Session ID length : empty (no session ID) */
4162 "004E" /* Cipher Suite Length : 78 bytes after this one */
4163 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4164 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4165 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4166 "000D" "000E" "000F" "0010" /* various bit lengths, */
4167 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4168 "0015" "0016" "0017" "0018"
4169 "0019" "001A" "001B" "002F"
4170 "0030" "0031" "0032" "0033"
4171 "0034" "0035" "0036" "0037"
4172 "0038" "0039" "003A"
4173 "01" /* Compression Length : 0x01 = 1 byte for types */
4174 "00" /* Compression Type : 0x00 = NULL compression */
4175 };
4176
4177 struct tcpcheck_ruleset *rs = NULL;
4178 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4179 struct tcpcheck_rule *chk;
4180 char *errmsg = NULL;
4181 int err_code = 0;
4182
4183 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4184 err_code |= ERR_WARN;
4185
4186 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4187 goto out;
4188
4189 curpx->options2 &= ~PR_O2_CHK_ANY;
4190 curpx->options2 |= PR_O2_TCPCHK_CHK;
4191
4192 free_tcpcheck_vars(&rules->preset_vars);
4193 rules->list = NULL;
4194 rules->flags = 0;
4195
4196 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4197 if (rs)
4198 goto ruleset_found;
4199
4200 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4201 if (rs == NULL) {
4202 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4203 goto error;
4204 }
4205
4206 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4207 1, curpx, &rs->rules, file, line, &errmsg);
4208 if (!chk) {
4209 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4210 goto error;
4211 }
4212 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004213 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004214
4215 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4216 "min-recv", "5", "ok-status", "L6OK",
4217 "error-status", "L6RSP", "tout-status", "L6TOUT",
4218 ""},
4219 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4220 if (!chk) {
4221 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4222 goto error;
4223 }
4224 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004225 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004226
4227 ruleset_found:
4228 rules->list = &rs->rules;
4229 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4230 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4231
4232 out:
4233 free(errmsg);
4234 return err_code;
4235
4236 error:
4237 free_tcpcheck_ruleset(rs);
4238 err_code |= ERR_ALERT | ERR_FATAL;
4239 goto out;
4240}
4241
4242/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004243int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004244 const char *file, int line)
4245{
4246 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4247
4248 struct tcpcheck_ruleset *rs = NULL;
4249 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4250 struct tcpcheck_rule *chk;
4251 struct tcpcheck_var *var = NULL;
4252 char *cmd = NULL, *errmsg = NULL;
4253 int err_code = 0;
4254
4255 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4256 err_code |= ERR_WARN;
4257
4258 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4259 goto out;
4260
4261 curpx->options2 &= ~PR_O2_CHK_ANY;
4262 curpx->options2 |= PR_O2_TCPCHK_CHK;
4263
4264 free_tcpcheck_vars(&rules->preset_vars);
4265 rules->list = NULL;
4266 rules->flags = 0;
4267
4268 cur_arg += 2;
4269 if (*args[cur_arg] && *args[cur_arg+1] &&
4270 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4271 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
Willy Tarreaua0fa5772023-04-07 15:06:42 +02004272 size_t len = strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1;
4273 cmd = calloc(len, 1);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004274 if (cmd)
Willy Tarreaua0fa5772023-04-07 15:06:42 +02004275 snprintf(cmd, len, "%s %s", args[cur_arg], args[cur_arg+1]);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004276 }
4277 else {
4278 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4279 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4280 cmd = strdup("HELO localhost");
4281 }
4282
4283 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4284 if (cmd == NULL || var == NULL) {
4285 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4286 goto error;
4287 }
4288 var->data.type = SMP_T_STR;
4289 var->data.u.str.area = cmd;
4290 var->data.u.str.data = strlen(cmd);
4291 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004292 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004293 cmd = NULL;
4294 var = NULL;
4295
4296 rs = find_tcpcheck_ruleset("*smtp-check");
4297 if (rs)
4298 goto ruleset_found;
4299
4300 rs = create_tcpcheck_ruleset("*smtp-check");
4301 if (rs == NULL) {
4302 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4303 goto error;
4304 }
4305
4306 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4307 1, curpx, &rs->rules, file, line, &errmsg);
4308 if (!chk) {
4309 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4310 goto error;
4311 }
4312 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004313 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004314
4315 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4316 "min-recv", "4",
4317 "error-status", "L7RSP",
4318 "on-error", "%[res.payload(0,0),cut_crlf]",
4319 ""},
4320 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4321 if (!chk) {
4322 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4323 goto error;
4324 }
4325 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004326 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004327
4328 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4329 "min-recv", "4",
4330 "error-status", "L7STS",
4331 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4332 "status-code", "res.payload(0,3)",
4333 ""},
4334 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4335 if (!chk) {
4336 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4337 goto error;
4338 }
4339 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004340 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004341
4342 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4343 1, curpx, &rs->rules, file, line, &errmsg);
4344 if (!chk) {
4345 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4346 goto error;
4347 }
4348 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004349 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004350
Christopher Faulet2ec1ffa2022-09-21 14:42:47 +02004351 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 +01004352 "error-status", "L7STS",
4353 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4354 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4355 "status-code", "res.payload(0,3)",
4356 ""},
4357 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4358 if (!chk) {
4359 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4360 goto error;
4361 }
4362 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004363 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004364
wrightlaw9a8d8a32022-09-08 16:10:48 +01004365 /* Send an SMTP QUIT to ensure clean disconnect (issue 1812), and expect a 2xx response code */
4366
4367 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "QUIT\r\n", ""},
4368 1, curpx, &rs->rules, file, line, &errmsg);
4369 if (!chk) {
4370 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4371 goto error;
4372 }
4373 chk->index = 5;
4374 LIST_APPEND(&rs->rules, &chk->list);
4375
4376 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4377 "min-recv", "4",
4378 "error-status", "L7STS",
4379 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4380 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4381 "status-code", "res.payload(0,3)",
4382 ""},
4383 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4384 if (!chk) {
4385 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4386 goto error;
4387 }
4388 chk->index = 6;
4389 LIST_APPEND(&rs->rules, &chk->list);
4390
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004391 ruleset_found:
4392 rules->list = &rs->rules;
4393 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4394 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4395
4396 out:
4397 free(errmsg);
4398 return err_code;
4399
4400 error:
4401 free(cmd);
4402 free(var);
4403 free_tcpcheck_vars(&rules->preset_vars);
4404 free_tcpcheck_ruleset(rs);
4405 err_code |= ERR_ALERT | ERR_FATAL;
4406 goto out;
4407}
4408
4409/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004410int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004411 const char *file, int line)
4412{
4413 static char pgsql_req[] = {
4414 "%[var(check.plen),htonl,hex]" /* The packet length*/
4415 "00030000" /* the version 3.0 */
4416 "7573657200" /* "user" key */
4417 "%[var(check.username),hex]00" /* the username */
4418 "00"
4419 };
4420
4421 struct tcpcheck_ruleset *rs = NULL;
4422 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4423 struct tcpcheck_rule *chk;
4424 struct tcpcheck_var *var = NULL;
4425 char *user = NULL, *errmsg = NULL;
4426 size_t packetlen = 0;
4427 int err_code = 0;
4428
4429 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4430 err_code |= ERR_WARN;
4431
4432 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4433 goto out;
4434
4435 curpx->options2 &= ~PR_O2_CHK_ANY;
4436 curpx->options2 |= PR_O2_TCPCHK_CHK;
4437
4438 free_tcpcheck_vars(&rules->preset_vars);
4439 rules->list = NULL;
4440 rules->flags = 0;
4441
4442 cur_arg += 2;
4443 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4444 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4445 file, line, args[0], args[1]);
4446 goto error;
4447 }
4448 if (strcmp(args[cur_arg], "user") == 0) {
4449 packetlen = 15 + strlen(args[cur_arg+1]);
4450 user = strdup(args[cur_arg+1]);
4451
4452 var = create_tcpcheck_var(ist("check.username"));
4453 if (user == NULL || var == NULL) {
4454 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4455 goto error;
4456 }
4457 var->data.type = SMP_T_STR;
4458 var->data.u.str.area = user;
4459 var->data.u.str.data = strlen(user);
4460 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004461 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004462 user = NULL;
4463 var = NULL;
4464
4465 var = create_tcpcheck_var(ist("check.plen"));
4466 if (var == NULL) {
4467 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4468 goto error;
4469 }
4470 var->data.type = SMP_T_SINT;
4471 var->data.u.sint = packetlen;
4472 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004473 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004474 var = NULL;
4475 }
4476 else {
4477 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4478 file, line, args[0], args[1]);
4479 goto error;
4480 }
4481
4482 rs = find_tcpcheck_ruleset("*pgsql-check");
4483 if (rs)
4484 goto ruleset_found;
4485
4486 rs = create_tcpcheck_ruleset("*pgsql-check");
4487 if (rs == NULL) {
4488 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4489 goto error;
4490 }
4491
4492 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4493 1, curpx, &rs->rules, file, line, &errmsg);
4494 if (!chk) {
4495 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4496 goto error;
4497 }
4498 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004499 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004500
4501 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4502 1, curpx, &rs->rules, file, line, &errmsg);
4503 if (!chk) {
4504 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4505 goto error;
4506 }
4507 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004508 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004509
4510 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4511 "min-recv", "5",
4512 "error-status", "L7RSP",
4513 "on-error", "%[res.payload(6,0)]",
4514 ""},
4515 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4516 if (!chk) {
4517 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4518 goto error;
4519 }
4520 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004521 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004522
Fatih Acar0d6fb7a2022-09-26 17:27:11 +02004523 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 +01004524 "min-recv", "9",
4525 "error-status", "L7STS",
4526 "on-success", "PostgreSQL server is ok",
4527 "on-error", "PostgreSQL unknown error",
4528 ""},
4529 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4530 if (!chk) {
4531 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4532 goto error;
4533 }
4534 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004535 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004536
4537 ruleset_found:
4538 rules->list = &rs->rules;
4539 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4540 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4541
4542 out:
4543 free(errmsg);
4544 return err_code;
4545
4546 error:
4547 free(user);
4548 free(var);
4549 free_tcpcheck_vars(&rules->preset_vars);
4550 free_tcpcheck_ruleset(rs);
4551 err_code |= ERR_ALERT | ERR_FATAL;
4552 goto out;
4553}
4554
4555
4556/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004557int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004558 const char *file, int line)
4559{
4560 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4561 * const char mysql40_client_auth_pkt[] = {
4562 * "\x0e\x00\x00" // packet length
4563 * "\x01" // packet number
4564 * "\x00\x00" // client capabilities
4565 * "\x00\x00\x01" // max packet
4566 * "haproxy\x00" // username (null terminated string)
4567 * "\x00" // filler (always 0x00)
4568 * "\x01\x00\x00" // packet length
4569 * "\x00" // packet number
4570 * "\x01" // COM_QUIT command
4571 * };
4572 */
4573 static char mysql40_rsname[] = "*mysql40-check";
4574 static char mysql40_req[] = {
4575 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4576 "0080" /* client capabilities */
4577 "000001" /* max packet */
4578 "%[var(check.username),hex]00" /* the username */
4579 "00" /* filler (always 0x00) */
4580 "010000" /* packet length*/
4581 "00" /* sequence ID */
4582 "01" /* COM_QUIT command */
4583 };
4584
4585 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4586 * const char mysql41_client_auth_pkt[] = {
4587 * "\x0e\x00\x00\" // packet length
4588 * "\x01" // packet number
4589 * "\x00\x00\x00\x00" // client capabilities
4590 * "\x00\x00\x00\x01" // max packet
4591 * "\x21" // character set (UTF-8)
4592 * char[23] // All zeroes
4593 * "haproxy\x00" // username (null terminated string)
4594 * "\x00" // filler (always 0x00)
4595 * "\x01\x00\x00" // packet length
4596 * "\x00" // packet number
4597 * "\x01" // COM_QUIT command
4598 * };
4599 */
4600 static char mysql41_rsname[] = "*mysql41-check";
4601 static char mysql41_req[] = {
4602 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4603 "00820000" /* client capabilities */
4604 "00800001" /* max packet */
4605 "21" /* character set (UTF-8) */
4606 "000000000000000000000000" /* 23 bytes, al zeroes */
4607 "0000000000000000000000"
4608 "%[var(check.username),hex]00" /* the username */
4609 "00" /* filler (always 0x00) */
4610 "010000" /* packet length*/
4611 "00" /* sequence ID */
4612 "01" /* COM_QUIT command */
4613 };
4614
4615 struct tcpcheck_ruleset *rs = NULL;
4616 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4617 struct tcpcheck_rule *chk;
4618 struct tcpcheck_var *var = NULL;
4619 char *mysql_rsname = "*mysql-check";
4620 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4621 int index = 0, err_code = 0;
4622
4623 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4624 err_code |= ERR_WARN;
4625
4626 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4627 goto out;
4628
4629 curpx->options2 &= ~PR_O2_CHK_ANY;
4630 curpx->options2 |= PR_O2_TCPCHK_CHK;
4631
4632 free_tcpcheck_vars(&rules->preset_vars);
4633 rules->list = NULL;
4634 rules->flags = 0;
4635
4636 cur_arg += 2;
4637 if (*args[cur_arg]) {
4638 int packetlen, userlen;
4639
4640 if (strcmp(args[cur_arg], "user") != 0) {
4641 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4642 file, line, args[0], args[1], args[cur_arg]);
4643 goto error;
4644 }
4645
4646 if (*(args[cur_arg+1]) == 0) {
4647 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4648 file, line, args[0], args[1], args[cur_arg]);
4649 goto error;
4650 }
4651
4652 hdr = calloc(4, sizeof(*hdr));
4653 user = strdup(args[cur_arg+1]);
4654 userlen = strlen(args[cur_arg+1]);
4655
4656 if (hdr == NULL || user == NULL) {
4657 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4658 goto error;
4659 }
4660
4661 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4662 packetlen = userlen + 7 + 27;
4663 mysql_req = mysql41_req;
4664 mysql_rsname = mysql41_rsname;
4665 }
4666 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4667 packetlen = userlen + 7;
4668 mysql_req = mysql40_req;
4669 mysql_rsname = mysql40_rsname;
4670 }
4671 else {
4672 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4673 file, line, args[cur_arg], args[cur_arg+2]);
4674 goto error;
4675 }
4676
4677 hdr[0] = (unsigned char)(packetlen & 0xff);
4678 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4679 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4680 hdr[3] = 1;
4681
4682 var = create_tcpcheck_var(ist("check.header"));
4683 if (var == NULL) {
4684 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4685 goto error;
4686 }
4687 var->data.type = SMP_T_STR;
4688 var->data.u.str.area = hdr;
4689 var->data.u.str.data = 4;
4690 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004691 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004692 hdr = NULL;
4693 var = NULL;
4694
4695 var = create_tcpcheck_var(ist("check.username"));
4696 if (var == NULL) {
4697 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4698 goto error;
4699 }
4700 var->data.type = SMP_T_STR;
4701 var->data.u.str.area = user;
4702 var->data.u.str.data = strlen(user);
4703 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004704 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004705 user = NULL;
4706 var = NULL;
4707 }
4708
4709 rs = find_tcpcheck_ruleset(mysql_rsname);
4710 if (rs)
4711 goto ruleset_found;
4712
4713 rs = create_tcpcheck_ruleset(mysql_rsname);
4714 if (rs == NULL) {
4715 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4716 goto error;
4717 }
4718
4719 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4720 1, curpx, &rs->rules, file, line, &errmsg);
4721 if (!chk) {
4722 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4723 goto error;
4724 }
4725 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004726 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004727
4728 if (mysql_req) {
4729 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4730 1, curpx, &rs->rules, file, line, &errmsg);
4731 if (!chk) {
4732 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4733 goto error;
4734 }
4735 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004736 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004737 }
4738
4739 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4740 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4741 if (!chk) {
4742 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4743 goto error;
4744 }
4745 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4746 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004747 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004748
4749 if (mysql_req) {
4750 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4751 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4752 if (!chk) {
4753 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4754 goto error;
4755 }
4756 chk->expect.custom = tcpcheck_mysql_expect_ok;
4757 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004758 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004759 }
4760
4761 ruleset_found:
4762 rules->list = &rs->rules;
4763 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4764 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4765
4766 out:
4767 free(errmsg);
4768 return err_code;
4769
4770 error:
4771 free(hdr);
4772 free(user);
4773 free(var);
4774 free_tcpcheck_vars(&rules->preset_vars);
4775 free_tcpcheck_ruleset(rs);
4776 err_code |= ERR_ALERT | ERR_FATAL;
4777 goto out;
4778}
4779
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004780int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004781 const char *file, int line)
4782{
4783 static char *ldap_req = "300C020101600702010304008000";
4784
4785 struct tcpcheck_ruleset *rs = NULL;
4786 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4787 struct tcpcheck_rule *chk;
4788 char *errmsg = NULL;
4789 int err_code = 0;
4790
4791 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4792 err_code |= ERR_WARN;
4793
4794 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4795 goto out;
4796
4797 curpx->options2 &= ~PR_O2_CHK_ANY;
4798 curpx->options2 |= PR_O2_TCPCHK_CHK;
4799
4800 free_tcpcheck_vars(&rules->preset_vars);
4801 rules->list = NULL;
4802 rules->flags = 0;
4803
4804 rs = find_tcpcheck_ruleset("*ldap-check");
4805 if (rs)
4806 goto ruleset_found;
4807
4808 rs = create_tcpcheck_ruleset("*ldap-check");
4809 if (rs == NULL) {
4810 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4811 goto error;
4812 }
4813
4814 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4815 1, curpx, &rs->rules, file, line, &errmsg);
4816 if (!chk) {
4817 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4818 goto error;
4819 }
4820 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004821 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004822
4823 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4824 "min-recv", "14",
4825 "on-error", "Not LDAPv3 protocol",
4826 ""},
4827 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4828 if (!chk) {
4829 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4830 goto error;
4831 }
4832 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004833 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004834
4835 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4836 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4837 if (!chk) {
4838 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4839 goto error;
4840 }
4841 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4842 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004843 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004844
4845 ruleset_found:
4846 rules->list = &rs->rules;
4847 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4848 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4849
4850 out:
4851 free(errmsg);
4852 return err_code;
4853
4854 error:
4855 free_tcpcheck_ruleset(rs);
4856 err_code |= ERR_ALERT | ERR_FATAL;
4857 goto out;
4858}
4859
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004860int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004861 const char *file, int line)
4862{
4863 struct tcpcheck_ruleset *rs = NULL;
4864 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4865 struct tcpcheck_rule *chk;
4866 char *spop_req = NULL;
4867 char *errmsg = NULL;
4868 int spop_len = 0, err_code = 0;
4869
4870 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4871 err_code |= ERR_WARN;
4872
4873 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4874 goto out;
4875
4876 curpx->options2 &= ~PR_O2_CHK_ANY;
4877 curpx->options2 |= PR_O2_TCPCHK_CHK;
4878
4879 free_tcpcheck_vars(&rules->preset_vars);
4880 rules->list = NULL;
4881 rules->flags = 0;
4882
4883
4884 rs = find_tcpcheck_ruleset("*spop-check");
4885 if (rs)
4886 goto ruleset_found;
4887
4888 rs = create_tcpcheck_ruleset("*spop-check");
4889 if (rs == NULL) {
4890 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4891 goto error;
4892 }
4893
4894 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4895 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4896 goto error;
4897 }
4898 chunk_reset(&trash);
4899 dump_binary(&trash, spop_req, spop_len);
4900 trash.area[trash.data] = '\0';
4901
4902 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4903 1, curpx, &rs->rules, file, line, &errmsg);
4904 if (!chk) {
4905 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4906 goto error;
4907 }
4908 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004909 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004910
4911 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4912 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4913 if (!chk) {
4914 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4915 goto error;
4916 }
4917 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4918 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004919 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004920
4921 ruleset_found:
4922 rules->list = &rs->rules;
4923 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4924 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4925
4926 out:
4927 free(spop_req);
4928 free(errmsg);
4929 return err_code;
4930
4931 error:
4932 free_tcpcheck_ruleset(rs);
4933 err_code |= ERR_ALERT | ERR_FATAL;
4934 goto out;
4935}
4936
4937
4938static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4939{
4940 struct tcpcheck_rule *chk = NULL;
4941 struct tcpcheck_http_hdr *hdr = NULL;
4942 char *meth = NULL, *uri = NULL, *vsn = NULL;
4943 char *hdrs, *body;
4944
4945 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4946 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004947 if (hdrs || body) {
Christopher Faulet4b5f3022022-09-05 09:05:17 +02004948 memprintf(errmsg, "hiding headers or body at the end of the version string is unsupported."
4949 "Use 'http-check send' directive instead.");
4950 goto error;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004951 }
4952
4953 chk = calloc(1, sizeof(*chk));
4954 if (!chk) {
4955 memprintf(errmsg, "out of memory");
4956 goto error;
4957 }
4958 chk->action = TCPCHK_ACT_SEND;
4959 chk->send.type = TCPCHK_SEND_HTTP;
4960 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4961 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4962 LIST_INIT(&chk->send.http.hdrs);
4963
4964 /* Copy the method, uri and version */
4965 if (*args[cur_arg]) {
4966 if (!*args[cur_arg+1])
4967 uri = args[cur_arg];
4968 else
4969 meth = args[cur_arg];
4970 }
4971 if (*args[cur_arg+1])
4972 uri = args[cur_arg+1];
4973 if (*args[cur_arg+2])
4974 vsn = args[cur_arg+2];
4975
4976 if (meth) {
4977 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4978 chk->send.http.meth.str.area = strdup(meth);
4979 chk->send.http.meth.str.data = strlen(meth);
4980 if (!chk->send.http.meth.str.area) {
4981 memprintf(errmsg, "out of memory");
4982 goto error;
4983 }
4984 }
4985 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004986 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004987 if (!isttest(chk->send.http.uri)) {
4988 memprintf(errmsg, "out of memory");
4989 goto error;
4990 }
4991 }
4992 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004993 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004994 if (!isttest(chk->send.http.vsn)) {
4995 memprintf(errmsg, "out of memory");
4996 goto error;
4997 }
4998 }
4999
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005000 return chk;
5001
5002 error:
5003 free_tcpcheck_http_hdr(hdr);
5004 free_tcpcheck(chk, 0);
5005 return NULL;
5006}
5007
5008/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005009int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005010 const char *file, int line)
5011{
5012 struct tcpcheck_ruleset *rs = NULL;
5013 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5014 struct tcpcheck_rule *chk;
5015 char *errmsg = NULL;
5016 int err_code = 0;
5017
5018 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5019 err_code |= ERR_WARN;
5020
5021 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5022 goto out;
5023
5024 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5025 if (!chk) {
5026 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5027 goto error;
5028 }
5029 if (errmsg) {
5030 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5031 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005032 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005033 }
5034
5035 no_request:
5036 curpx->options2 &= ~PR_O2_CHK_ANY;
5037 curpx->options2 |= PR_O2_TCPCHK_CHK;
5038
5039 free_tcpcheck_vars(&rules->preset_vars);
5040 rules->list = NULL;
5041 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5042
5043 /* Deduce the ruleset name from the proxy info */
5044 chunk_printf(&trash, "*http-check-%s_%s-%d",
5045 ((curpx == defpx) ? "defaults" : curpx->id),
5046 curpx->conf.file, curpx->conf.line);
5047
5048 rs = find_tcpcheck_ruleset(b_orig(&trash));
5049 if (rs == NULL) {
5050 rs = create_tcpcheck_ruleset(b_orig(&trash));
5051 if (rs == NULL) {
5052 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5053 goto error;
5054 }
5055 }
5056
5057 rules->list = &rs->rules;
5058 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5059 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5060 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5061 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5062 rules->list = NULL;
5063 goto error;
5064 }
5065
5066 out:
5067 free(errmsg);
5068 return err_code;
5069
5070 error:
5071 free_tcpcheck_ruleset(rs);
5072 free_tcpcheck(chk, 0);
5073 err_code |= ERR_ALERT | ERR_FATAL;
5074 goto out;
5075}
5076
5077/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005078int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005079 const char *file, int line)
5080{
5081 struct tcpcheck_ruleset *rs = NULL;
5082 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5083 int err_code = 0;
5084
5085 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5086 err_code |= ERR_WARN;
5087
5088 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5089 goto out;
5090
5091 curpx->options2 &= ~PR_O2_CHK_ANY;
5092 curpx->options2 |= PR_O2_TCPCHK_CHK;
5093
5094 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5095 /* If a tcp-check rulesset is already set, do nothing */
5096 if (rules->list)
5097 goto out;
5098
5099 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5100 * get it.
5101 */
5102 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5103 goto curpx_ruleset;
5104
5105 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5106 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5107 rs = find_tcpcheck_ruleset(b_orig(&trash));
5108 if (rs)
5109 goto ruleset_found;
5110 }
5111
5112 curpx_ruleset:
5113 /* Deduce the ruleset name from the proxy info */
5114 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5115 ((curpx == defpx) ? "defaults" : curpx->id),
5116 curpx->conf.file, curpx->conf.line);
5117
5118 rs = find_tcpcheck_ruleset(b_orig(&trash));
5119 if (rs == NULL) {
5120 rs = create_tcpcheck_ruleset(b_orig(&trash));
5121 if (rs == NULL) {
5122 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5123 goto error;
5124 }
5125 }
5126
5127 ruleset_found:
5128 free_tcpcheck_vars(&rules->preset_vars);
5129 rules->list = &rs->rules;
5130 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5131 rules->flags |= TCPCHK_RULES_TCP_CHK;
5132
5133 out:
5134 return err_code;
5135
5136 error:
5137 err_code |= ERR_ALERT | ERR_FATAL;
5138 goto out;
5139}
5140
Willy Tarreau51cd5952020-06-05 12:25:38 +02005141static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005142 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005143 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5144 { 0, NULL, NULL },
5145}};
5146
5147REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5148REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5149REGISTER_POST_DEINIT(deinit_tcpchecks);
5150INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);