blob: 085f42768a17389da26f096395ad10efc93d86c4 [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:
2050 if (expect->custom)
2051 ret = expect->custom(check, rule, last_read);
2052 goto out;
2053 default:
2054 /* Should never happen. */
2055 ret = TCPCHK_EVAL_STOP;
2056 goto out;
2057 }
2058
2059
2060 /* Wait for more data on mismatch only if no minimum is defined (-1),
2061 * otherwise the absence of match is already conclusive.
2062 */
2063 if (!match && !last_read && (expect->min_recv == -1)) {
2064 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002065 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002066 goto out;
2067 }
2068
2069 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002070 if (match ^ inverse) {
2071 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002072 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002073 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002074
2075 error:
2076 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002077 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002078 ret = TCPCHK_EVAL_STOP;
2079 msg = alloc_trash_chunk();
2080 if (msg)
2081 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2082 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2083 free_trash_chunk(msg);
2084
2085 out:
2086 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002087 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002088 return ret;
2089}
2090
2091/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2092 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2093 * waits.
2094 */
2095enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2096{
2097 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2098 struct act_rule *act_rule;
2099 enum act_return act_ret;
2100
2101 act_rule =rule->action_kw.rule;
2102 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2103 if (act_ret != ACT_RET_CONT) {
2104 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2105 tcpcheck_get_step_id(check, rule));
2106 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2107 ret = TCPCHK_EVAL_STOP;
2108 }
2109
2110 return ret;
2111}
2112
2113/* Executes a tcp-check ruleset. Note that this is called both from the
2114 * connection's wake() callback and from the check scheduling task. It returns
2115 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2116 * presenting the risk of an fd replacement.
2117 *
2118 * Please do NOT place any return statement in this function and only leave
2119 * via the out_end_tcpcheck label after setting retcode.
2120 */
2121int tcpcheck_main(struct check *check)
2122{
2123 struct tcpcheck_rule *rule;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002124 struct stconn *sc = check->sc;
2125 struct connection *conn = sc_conn(sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002126 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002127 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002128 enum tcpcheck_eval_ret eval_ret;
2129
2130 /* here, we know that the check is complete or that it failed */
2131 if (check->result != CHK_RES_UNKNOWN)
2132 goto out;
2133
Christopher Faulet147b8c92021-04-10 09:00:38 +02002134 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2135
Willy Tarreau4596fe22022-05-17 19:07:51 +02002136 /* Note: the stream connector and the connection may only be undefined before
Willy Tarreau51cd5952020-06-05 12:25:38 +02002137 * the first rule evaluation (it is always a connect rule) or when the
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002138 * stream connector allocation failed on a connect rule, during sc allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002139 */
2140
2141 /* 1- check for connection error, if any */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002142 if ((conn && conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002143 goto out_end_tcpcheck;
2144
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002145 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002146 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002147 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002148 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002149 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2150 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002151
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002152 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002153 * tcp-check variables */
2154 else {
2155 struct tcpcheck_var *var;
2156
2157 /* First evaluation, create a session */
2158 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2159 if (!check->sess) {
2160 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002161 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002162 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2163 goto out_end_tcpcheck;
2164 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002165 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002166 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2167
2168 /* Preset tcp-check variables */
2169 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2170 struct sample smp;
2171
2172 memset(&smp, 0, sizeof(smp));
2173 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2174 smp.data = var->data;
2175 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2176 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002177 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002178 }
2179
2180 /* Now evaluate the tcp-check rules */
2181
2182 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2183 check->code = 0;
2184 switch (rule->action) {
2185 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002186 /* Not the first connection, release it first */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002187 if (sc_conn(sc) && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002188 check->state |= CHK_ST_CLOSE_CONN;
2189 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002190 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002191
2192 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002193
2194 /* We are still waiting the connection gets closed */
Christopher Faulet560b8da2022-05-30 08:37:39 +02002195 if (check->state & CHK_ST_CLOSE_CONN) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002196 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002197 eval_ret = TCPCHK_EVAL_WAIT;
2198 break;
2199 }
2200
Christopher Faulet147b8c92021-04-10 09:00:38 +02002201 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002202 eval_ret = tcpcheck_eval_connect(check, rule);
2203
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002204 /* Refresh connection */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002205 conn = sc_conn(sc);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002206 last_read = 0;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002207 must_read = (IS_HTX_SC(sc) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002208 break;
2209 case TCPCHK_ACT_SEND:
2210 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002211 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002212 eval_ret = tcpcheck_eval_send(check, rule);
2213 must_read = 1;
2214 break;
2215 case TCPCHK_ACT_EXPECT:
2216 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002217 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002218 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002219 eval_ret = tcpcheck_eval_recv(check, rule);
2220 if (eval_ret == TCPCHK_EVAL_STOP)
2221 goto out_end_tcpcheck;
2222 else if (eval_ret == TCPCHK_EVAL_WAIT)
2223 goto out;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002224 last_read = ((conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR | SE_FL_EOS));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002225 must_read = 0;
2226 }
2227
2228 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2229 ? tcpcheck_eval_expect_http(check, rule, last_read)
2230 : tcpcheck_eval_expect(check, rule, last_read));
2231
2232 if (eval_ret == TCPCHK_EVAL_WAIT) {
2233 check->current_step = rule->expect.head;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002234 if (!(sc->wait_event.events & SUB_RETRY_RECV))
2235 conn->mux->subscribe(sc, SUB_RETRY_RECV, &sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002236 }
2237 break;
2238 case TCPCHK_ACT_ACTION_KW:
2239 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002240 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002241 eval_ret = tcpcheck_eval_action_kw(check, rule);
2242 break;
2243 default:
2244 /* Otherwise, just go to the next one and don't update
2245 * the current step
2246 */
2247 eval_ret = TCPCHK_EVAL_CONTINUE;
2248 break;
2249 }
2250
2251 switch (eval_ret) {
2252 case TCPCHK_EVAL_CONTINUE:
2253 break;
2254 case TCPCHK_EVAL_WAIT:
2255 goto out;
2256 case TCPCHK_EVAL_STOP:
2257 goto out_end_tcpcheck;
2258 }
2259 }
2260
2261 /* All rules was evaluated */
2262 if (check->current_step) {
2263 rule = check->current_step;
2264
Christopher Faulet147b8c92021-04-10 09:00:38 +02002265 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2266
Willy Tarreau51cd5952020-06-05 12:25:38 +02002267 if (rule->action == TCPCHK_ACT_EXPECT) {
2268 struct buffer *msg;
2269 enum healthcheck_status status;
2270
2271 if (check->server &&
2272 (check->server->proxy->options & PR_O_DISABLE404) &&
2273 (check->server->next_state != SRV_ST_STOPPED) &&
2274 (check->code == 404)) {
2275 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002276 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002277 goto out_end_tcpcheck;
2278 }
2279
2280 msg = alloc_trash_chunk();
2281 if (msg)
2282 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2283 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2284 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2285 free_trash_chunk(msg);
2286 }
2287 else if (rule->action == TCPCHK_ACT_CONNECT) {
2288 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2289 enum healthcheck_status status = HCHK_STATUS_L4OK;
2290#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002291 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002292 status = HCHK_STATUS_L6OK;
2293#endif
2294 set_server_check_status(check, status, msg);
2295 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002296 else
2297 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002298 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002299 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002300 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002301 }
2302 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002303
2304 out_end_tcpcheck:
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002305 if ((conn && conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002306 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002307 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002308 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002309
Christopher Fauletb381a502020-11-25 13:47:00 +01002310 /* the tcpcheck is finished, release in/out buffer now */
2311 check_release_buf(check, &check->bi);
2312 check_release_buf(check, &check->bo);
2313
Willy Tarreau51cd5952020-06-05 12:25:38 +02002314 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002315 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002316 return retcode;
2317}
2318
Willy Tarreaua631b862022-03-02 14:54:44 +01002319void tcp_check_keywords_register(struct action_kw_list *kw_list)
2320{
2321 LIST_APPEND(&tcp_check_keywords.list, &kw_list->list);
2322}
Willy Tarreau51cd5952020-06-05 12:25:38 +02002323
2324/**************************************************************************/
2325/******************* Internals to parse tcp-check rules *******************/
2326/**************************************************************************/
2327struct action_kw_list tcp_check_keywords = {
2328 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2329};
2330
2331/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2332 * returned on error.
2333 */
2334struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2335 struct list *rules, struct action_kw *kw,
2336 const char *file, int line, char **errmsg)
2337{
2338 struct tcpcheck_rule *chk = NULL;
2339 struct act_rule *actrule = NULL;
2340
Willy Tarreaud535f802021-10-11 08:49:26 +02002341 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002342 if (!actrule) {
2343 memprintf(errmsg, "out of memory");
2344 goto error;
2345 }
2346 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002347
2348 cur_arg++;
2349 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2350 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2351 goto error;
2352 }
2353
2354 chk = calloc(1, sizeof(*chk));
2355 if (!chk) {
2356 memprintf(errmsg, "out of memory");
2357 goto error;
2358 }
2359 chk->action = TCPCHK_ACT_ACTION_KW;
2360 chk->action_kw.rule = actrule;
2361 return chk;
2362
2363 error:
2364 free(actrule);
2365 return NULL;
2366}
2367
2368/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2369 * returned on error.
2370 */
2371struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2372 const char *file, int line, char **errmsg)
2373{
2374 struct tcpcheck_rule *chk = NULL;
2375 struct sockaddr_storage *sk = NULL;
2376 char *comment = NULL, *sni = NULL, *alpn = NULL;
2377 struct sample_expr *port_expr = NULL;
2378 const struct mux_proto_list *mux_proto = NULL;
2379 unsigned short conn_opts = 0;
2380 long port = 0;
2381 int alpn_len = 0;
2382
2383 list_for_each_entry(chk, rules, list) {
2384 if (chk->action == TCPCHK_ACT_CONNECT)
2385 break;
2386 if (chk->action == TCPCHK_ACT_COMMENT ||
2387 chk->action == TCPCHK_ACT_ACTION_KW ||
2388 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2389 continue;
2390
2391 memprintf(errmsg, "first step MUST also be a 'connect', "
2392 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2393 "when there is a 'connect' step in the tcp-check ruleset");
2394 goto error;
2395 }
2396
2397 cur_arg++;
2398 while (*(args[cur_arg])) {
2399 if (strcmp(args[cur_arg], "default") == 0)
2400 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2401 else if (strcmp(args[cur_arg], "addr") == 0) {
2402 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002403
2404 if (!*(args[cur_arg+1])) {
2405 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2406 goto error;
2407 }
2408
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002409 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2410 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002411 if (!sk) {
2412 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2413 goto error;
2414 }
2415
Willy Tarreau51cd5952020-06-05 12:25:38 +02002416 cur_arg++;
2417 }
2418 else if (strcmp(args[cur_arg], "port") == 0) {
2419 const char *p, *end;
2420
2421 if (!*(args[cur_arg+1])) {
2422 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2423 goto error;
2424 }
2425 cur_arg++;
2426
2427 port = 0;
2428 release_sample_expr(port_expr);
2429 p = args[cur_arg]; end = p + strlen(p);
2430 port = read_uint(&p, end);
2431 if (p != end) {
2432 int idx = 0;
2433
2434 px->conf.args.ctx = ARGC_SRV;
2435 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002436 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002437
2438 if (!port_expr) {
2439 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2440 goto error;
2441 }
2442 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2443 memprintf(errmsg, "error detected while parsing port expression : "
2444 " fetch method '%s' extracts information from '%s', "
2445 "none of which is available here.\n",
2446 args[cur_arg], sample_src_names(port_expr->fetch->use));
2447 goto error;
2448 }
2449 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2450 }
2451 else if (port > 65535 || port < 1) {
2452 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2453 args[cur_arg]);
2454 goto error;
2455 }
2456 }
2457 else if (strcmp(args[cur_arg], "proto") == 0) {
2458 if (!*(args[cur_arg+1])) {
2459 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2460 goto error;
2461 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002462 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002463 if (!mux_proto) {
2464 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2465 goto error;
2466 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002467
2468 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2469 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2470 goto error;
2471 }
2472 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2473 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2474 goto error;
2475 }
2476
Willy Tarreau51cd5952020-06-05 12:25:38 +02002477 cur_arg++;
2478 }
2479 else if (strcmp(args[cur_arg], "comment") == 0) {
2480 if (!*(args[cur_arg+1])) {
2481 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2482 goto error;
2483 }
2484 cur_arg++;
2485 free(comment);
2486 comment = strdup(args[cur_arg]);
2487 if (!comment) {
2488 memprintf(errmsg, "out of memory");
2489 goto error;
2490 }
2491 }
2492 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2493 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2494 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2495 conn_opts |= TCPCHK_OPT_SOCKS4;
2496 else if (strcmp(args[cur_arg], "linger") == 0)
2497 conn_opts |= TCPCHK_OPT_LINGER;
2498#ifdef USE_OPENSSL
2499 else if (strcmp(args[cur_arg], "ssl") == 0) {
2500 px->options |= PR_O_TCPCHK_SSL;
2501 conn_opts |= TCPCHK_OPT_SSL;
2502 }
2503 else if (strcmp(args[cur_arg], "sni") == 0) {
2504 if (!*(args[cur_arg+1])) {
2505 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2506 goto error;
2507 }
2508 cur_arg++;
2509 free(sni);
2510 sni = strdup(args[cur_arg]);
2511 if (!sni) {
2512 memprintf(errmsg, "out of memory");
2513 goto error;
2514 }
2515 }
2516 else if (strcmp(args[cur_arg], "alpn") == 0) {
2517#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2518 free(alpn);
2519 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2520 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2521 goto error;
2522 }
2523 cur_arg++;
2524#else
2525 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2526 goto error;
2527#endif
2528 }
2529#endif /* USE_OPENSSL */
2530
2531 else {
2532 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2533#ifdef USE_OPENSSL
2534 ", 'ssl', 'sni', 'alpn'"
2535#endif /* USE_OPENSSL */
2536 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2537 args[cur_arg]);
2538 goto error;
2539 }
2540 cur_arg++;
2541 }
2542
2543 chk = calloc(1, sizeof(*chk));
2544 if (!chk) {
2545 memprintf(errmsg, "out of memory");
2546 goto error;
2547 }
2548 chk->action = TCPCHK_ACT_CONNECT;
2549 chk->comment = comment;
2550 chk->connect.port = port;
2551 chk->connect.options = conn_opts;
2552 chk->connect.sni = sni;
2553 chk->connect.alpn = alpn;
2554 chk->connect.alpn_len= alpn_len;
2555 chk->connect.port_expr= port_expr;
2556 chk->connect.mux_proto= mux_proto;
2557 if (sk)
2558 chk->connect.addr = *sk;
2559 return chk;
2560
2561 error:
2562 free(alpn);
2563 free(sni);
2564 free(comment);
2565 release_sample_expr(port_expr);
2566 return NULL;
2567}
2568
2569/* Parses and creates a tcp-check send rule. NULL is returned on error */
2570struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2571 const char *file, int line, char **errmsg)
2572{
2573 struct tcpcheck_rule *chk = NULL;
2574 char *comment = NULL, *data = NULL;
2575 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2576
2577 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2578 type = TCPCHK_SEND_BINARY_LF;
2579 else if (strcmp(args[cur_arg], "send-binary") == 0)
2580 type = TCPCHK_SEND_BINARY;
2581 else if (strcmp(args[cur_arg], "send-lf") == 0)
2582 type = TCPCHK_SEND_STRING_LF;
2583 else if (strcmp(args[cur_arg], "send") == 0)
2584 type = TCPCHK_SEND_STRING;
2585
2586 if (!*(args[cur_arg+1])) {
2587 memprintf(errmsg, "'%s' expects a %s as argument",
2588 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2589 goto error;
2590 }
2591
2592 data = args[cur_arg+1];
2593
2594 cur_arg += 2;
2595 while (*(args[cur_arg])) {
2596 if (strcmp(args[cur_arg], "comment") == 0) {
2597 if (!*(args[cur_arg+1])) {
2598 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2599 goto error;
2600 }
2601 cur_arg++;
2602 free(comment);
2603 comment = strdup(args[cur_arg]);
2604 if (!comment) {
2605 memprintf(errmsg, "out of memory");
2606 goto error;
2607 }
2608 }
2609 else {
2610 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2611 args[cur_arg]);
2612 goto error;
2613 }
2614 cur_arg++;
2615 }
2616
2617 chk = calloc(1, sizeof(*chk));
2618 if (!chk) {
2619 memprintf(errmsg, "out of memory");
2620 goto error;
2621 }
2622 chk->action = TCPCHK_ACT_SEND;
2623 chk->comment = comment;
2624 chk->send.type = type;
2625
2626 switch (chk->send.type) {
2627 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002628 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002629 if (!isttest(chk->send.data)) {
2630 memprintf(errmsg, "out of memory");
2631 goto error;
2632 }
2633 break;
2634 case TCPCHK_SEND_BINARY: {
2635 int len = chk->send.data.len;
2636 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2637 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2638 goto error;
2639 }
2640 chk->send.data.len = len;
2641 break;
2642 }
2643 case TCPCHK_SEND_STRING_LF:
2644 case TCPCHK_SEND_BINARY_LF:
2645 LIST_INIT(&chk->send.fmt);
2646 px->conf.args.ctx = ARGC_SRV;
2647 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2648 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2649 goto error;
2650 }
2651 break;
2652 case TCPCHK_SEND_HTTP:
2653 case TCPCHK_SEND_UNDEF:
2654 goto error;
2655 }
2656
2657 return chk;
2658
2659 error:
2660 free(chk);
2661 free(comment);
2662 return NULL;
2663}
2664
2665/* Parses and creates a http-check send rule. NULL is returned on error */
2666struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2667 const char *file, int line, char **errmsg)
2668{
2669 struct tcpcheck_rule *chk = NULL;
2670 struct tcpcheck_http_hdr *hdr = NULL;
2671 struct http_hdr hdrs[global.tune.max_http_hdr];
2672 char *meth = NULL, *uri = NULL, *vsn = NULL;
2673 char *body = NULL, *comment = NULL;
2674 unsigned int flags = 0;
2675 int i = 0, host_hdr = -1;
2676
2677 cur_arg++;
2678 while (*(args[cur_arg])) {
2679 if (strcmp(args[cur_arg], "meth") == 0) {
2680 if (!*(args[cur_arg+1])) {
2681 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2682 goto error;
2683 }
2684 cur_arg++;
2685 meth = args[cur_arg];
2686 }
2687 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2688 if (!*(args[cur_arg+1])) {
2689 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2690 goto error;
2691 }
2692 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2693 if (strcmp(args[cur_arg], "uri-lf") == 0)
2694 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2695 cur_arg++;
2696 uri = args[cur_arg];
2697 }
2698 else if (strcmp(args[cur_arg], "ver") == 0) {
2699 if (!*(args[cur_arg+1])) {
2700 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2701 goto error;
2702 }
2703 cur_arg++;
2704 vsn = args[cur_arg];
2705 }
2706 else if (strcmp(args[cur_arg], "hdr") == 0) {
2707 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2708 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2709 goto error;
2710 }
2711
2712 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2713 if (host_hdr >= 0) {
2714 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2715 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2716 goto error;
2717 }
2718 host_hdr = i;
2719 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002720 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002721 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2722 goto skip_hdr;
2723
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002724 hdrs[i].n = ist(args[cur_arg + 1]);
2725 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002726 i++;
2727 skip_hdr:
2728 cur_arg += 2;
2729 }
2730 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2731 if (!*(args[cur_arg+1])) {
2732 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2733 goto error;
2734 }
2735 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2736 if (strcmp(args[cur_arg], "body-lf") == 0)
2737 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2738 cur_arg++;
2739 body = args[cur_arg];
2740 }
2741 else if (strcmp(args[cur_arg], "comment") == 0) {
2742 if (!*(args[cur_arg+1])) {
2743 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2744 goto error;
2745 }
2746 cur_arg++;
2747 free(comment);
2748 comment = strdup(args[cur_arg]);
2749 if (!comment) {
2750 memprintf(errmsg, "out of memory");
2751 goto error;
2752 }
2753 }
2754 else {
2755 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2756 " but got '%s' as argument.", args[cur_arg]);
2757 goto error;
2758 }
2759 cur_arg++;
2760 }
2761
2762 hdrs[i].n = hdrs[i].v = IST_NULL;
2763
2764 chk = calloc(1, sizeof(*chk));
2765 if (!chk) {
2766 memprintf(errmsg, "out of memory");
2767 goto error;
2768 }
2769 chk->action = TCPCHK_ACT_SEND;
2770 chk->comment = comment; comment = NULL;
2771 chk->send.type = TCPCHK_SEND_HTTP;
2772 chk->send.http.flags = flags;
2773 LIST_INIT(&chk->send.http.hdrs);
2774
2775 if (meth) {
2776 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2777 chk->send.http.meth.str.area = strdup(meth);
2778 chk->send.http.meth.str.data = strlen(meth);
2779 if (!chk->send.http.meth.str.area) {
2780 memprintf(errmsg, "out of memory");
2781 goto error;
2782 }
2783 }
2784 if (uri) {
2785 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2786 LIST_INIT(&chk->send.http.uri_fmt);
2787 px->conf.args.ctx = ARGC_SRV;
2788 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2789 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2790 goto error;
2791 }
2792 }
2793 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002794 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002795 if (!isttest(chk->send.http.uri)) {
2796 memprintf(errmsg, "out of memory");
2797 goto error;
2798 }
2799 }
2800 }
2801 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002802 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002803 if (!isttest(chk->send.http.vsn)) {
2804 memprintf(errmsg, "out of memory");
2805 goto error;
2806 }
2807 }
2808 for (i = 0; istlen(hdrs[i].n); i++) {
2809 hdr = calloc(1, sizeof(*hdr));
2810 if (!hdr) {
2811 memprintf(errmsg, "out of memory");
2812 goto error;
2813 }
2814 LIST_INIT(&hdr->value);
2815 hdr->name = istdup(hdrs[i].n);
2816 if (!isttest(hdr->name)) {
2817 memprintf(errmsg, "out of memory");
2818 goto error;
2819 }
2820
2821 ist0(hdrs[i].v);
2822 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2823 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002824 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002825 hdr = NULL;
2826 }
2827
2828 if (body) {
2829 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2830 LIST_INIT(&chk->send.http.body_fmt);
2831 px->conf.args.ctx = ARGC_SRV;
2832 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2833 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2834 goto error;
2835 }
2836 }
2837 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002838 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002839 if (!isttest(chk->send.http.body)) {
2840 memprintf(errmsg, "out of memory");
2841 goto error;
2842 }
2843 }
2844 }
2845
2846 return chk;
2847
2848 error:
2849 free_tcpcheck_http_hdr(hdr);
2850 free_tcpcheck(chk, 0);
2851 free(comment);
2852 return NULL;
2853}
2854
2855/* Parses and creates a http-check comment rule. NULL is returned on error */
2856struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2857 const char *file, int line, char **errmsg)
2858{
2859 struct tcpcheck_rule *chk = NULL;
2860 char *comment = NULL;
2861
2862 if (!*(args[cur_arg+1])) {
2863 memprintf(errmsg, "expects a string as argument");
2864 goto error;
2865 }
2866 cur_arg++;
2867 comment = strdup(args[cur_arg]);
2868 if (!comment) {
2869 memprintf(errmsg, "out of memory");
2870 goto error;
2871 }
2872
2873 chk = calloc(1, sizeof(*chk));
2874 if (!chk) {
2875 memprintf(errmsg, "out of memory");
2876 goto error;
2877 }
2878 chk->action = TCPCHK_ACT_COMMENT;
2879 chk->comment = comment;
2880 return chk;
2881
2882 error:
2883 free(comment);
2884 return NULL;
2885}
2886
2887/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2888 * on error. <proto> is set to the right protocol flags (covered by the
2889 * TCPCHK_RULES_PROTO_CHK mask).
2890 */
2891struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2892 struct list *rules, unsigned int proto,
2893 const char *file, int line, char **errmsg)
2894{
2895 struct tcpcheck_rule *prev_check, *chk = NULL;
2896 struct sample_expr *status_expr = NULL;
2897 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2898 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2899 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2900 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2901 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2902 unsigned int flags = 0;
2903 long min_recv = -1;
2904 int inverse = 0;
2905
2906 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2907 if (!*(args[cur_arg+1])) {
2908 memprintf(errmsg, "expects at least a matching pattern as arguments");
2909 goto error;
2910 }
2911
2912 cur_arg++;
2913 while (*(args[cur_arg])) {
2914 int in_pattern = 0;
2915
2916 rescan:
2917 if (strcmp(args[cur_arg], "min-recv") == 0) {
2918 if (in_pattern) {
2919 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2920 goto error;
2921 }
2922 if (!*(args[cur_arg+1])) {
2923 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2924 goto error;
2925 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002926 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002927 cur_arg++;
2928 min_recv = atol(args[cur_arg]);
2929 if (min_recv < -1 || min_recv > INT_MAX) {
2930 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2931 goto error;
2932 }
2933 }
2934 else if (*(args[cur_arg]) == '!') {
2935 in_pattern = 1;
2936 while (*(args[cur_arg]) == '!') {
2937 inverse = !inverse;
2938 args[cur_arg]++;
2939 }
2940 if (!*(args[cur_arg]))
2941 cur_arg++;
2942 goto rescan;
2943 }
2944 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2945 if (type != TCPCHK_EXPECT_UNDEF) {
2946 memprintf(errmsg, "only on pattern expected");
2947 goto error;
2948 }
2949 if (proto != TCPCHK_RULES_HTTP_CHK)
2950 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2951 else
2952 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2953
2954 if (!*(args[cur_arg+1])) {
2955 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2956 goto error;
2957 }
2958 cur_arg++;
2959 pattern = args[cur_arg];
2960 }
2961 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2962 if (proto == TCPCHK_RULES_HTTP_CHK)
2963 goto bad_http_kw;
2964 if (type != TCPCHK_EXPECT_UNDEF) {
2965 memprintf(errmsg, "only on pattern expected");
2966 goto error;
2967 }
2968 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2969
2970 if (!*(args[cur_arg+1])) {
2971 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2972 goto error;
2973 }
2974 cur_arg++;
2975 pattern = args[cur_arg];
2976 }
2977 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2978 if (type != TCPCHK_EXPECT_UNDEF) {
2979 memprintf(errmsg, "only on pattern expected");
2980 goto error;
2981 }
2982 if (proto != TCPCHK_RULES_HTTP_CHK)
2983 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2984 else {
2985 if (*(args[cur_arg]) != 's')
2986 goto bad_http_kw;
2987 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2988 }
2989
2990 if (!*(args[cur_arg+1])) {
2991 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2992 goto error;
2993 }
2994 cur_arg++;
2995 pattern = args[cur_arg];
2996 }
2997 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
2998 if (proto != TCPCHK_RULES_HTTP_CHK)
2999 goto bad_tcp_kw;
3000 if (type != TCPCHK_EXPECT_UNDEF) {
3001 memprintf(errmsg, "only on pattern expected");
3002 goto error;
3003 }
3004 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3005
3006 if (!*(args[cur_arg+1])) {
3007 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3008 goto error;
3009 }
3010 cur_arg++;
3011 pattern = args[cur_arg];
3012 }
3013 else if (strcmp(args[cur_arg], "custom") == 0) {
3014 if (in_pattern) {
3015 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3016 goto error;
3017 }
3018 if (type != TCPCHK_EXPECT_UNDEF) {
3019 memprintf(errmsg, "only on pattern expected");
3020 goto error;
3021 }
3022 type = TCPCHK_EXPECT_CUSTOM;
3023 }
3024 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3025 int orig_arg = cur_arg;
3026
3027 if (proto != TCPCHK_RULES_HTTP_CHK)
3028 goto bad_tcp_kw;
3029 if (type != TCPCHK_EXPECT_UNDEF) {
3030 memprintf(errmsg, "only on pattern expected");
3031 goto error;
3032 }
3033 type = TCPCHK_EXPECT_HTTP_HEADER;
3034
3035 if (strcmp(args[cur_arg], "fhdr") == 0)
3036 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3037
3038 /* Parse the name pattern, mandatory */
3039 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3040 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3041 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3042 args[orig_arg]);
3043 goto error;
3044 }
3045
3046 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3047 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3048
3049 cur_arg += 2;
3050 if (strcmp(args[cur_arg], "-m") == 0) {
3051 if (!*(args[cur_arg+1])) {
3052 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3053 args[orig_arg], args[cur_arg]);
3054 goto error;
3055 }
3056 if (strcmp(args[cur_arg+1], "str") == 0)
3057 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3058 else if (strcmp(args[cur_arg+1], "beg") == 0)
3059 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3060 else if (strcmp(args[cur_arg+1], "end") == 0)
3061 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3062 else if (strcmp(args[cur_arg+1], "sub") == 0)
3063 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3064 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3065 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3066 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3067 args[orig_arg]);
3068 goto error;
3069 }
3070 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3071 }
3072 else {
3073 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3074 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3075 goto error;
3076 }
3077 cur_arg += 2;
3078 }
3079 else
3080 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3081 npat = args[cur_arg];
3082
3083 if (!*(args[cur_arg+1]) ||
3084 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3085 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3086 goto next;
3087 }
3088 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3089 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3090
3091 /* Parse the value pattern, optional */
3092 if (strcmp(args[cur_arg+2], "-m") == 0) {
3093 cur_arg += 2;
3094 if (!*(args[cur_arg+1])) {
3095 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3096 args[orig_arg], args[cur_arg]);
3097 goto error;
3098 }
3099 if (strcmp(args[cur_arg+1], "str") == 0)
3100 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3101 else if (strcmp(args[cur_arg+1], "beg") == 0)
3102 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3103 else if (strcmp(args[cur_arg+1], "end") == 0)
3104 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3105 else if (strcmp(args[cur_arg+1], "sub") == 0)
3106 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3107 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3108 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3109 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3110 args[orig_arg]);
3111 goto error;
3112 }
3113 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3114 }
3115 else {
3116 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3117 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3118 goto error;
3119 }
3120 }
3121 else
3122 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3123
3124 if (!*(args[cur_arg+2])) {
3125 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3126 goto error;
3127 }
3128 vpat = args[cur_arg+2];
3129 cur_arg += 2;
3130 }
3131 else if (strcmp(args[cur_arg], "comment") == 0) {
3132 if (in_pattern) {
3133 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3134 goto error;
3135 }
3136 if (!*(args[cur_arg+1])) {
3137 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3138 goto error;
3139 }
3140 cur_arg++;
3141 free(comment);
3142 comment = strdup(args[cur_arg]);
3143 if (!comment) {
3144 memprintf(errmsg, "out of memory");
3145 goto error;
3146 }
3147 }
3148 else if (strcmp(args[cur_arg], "on-success") == 0) {
3149 if (in_pattern) {
3150 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3151 goto error;
3152 }
3153 if (!*(args[cur_arg+1])) {
3154 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3155 goto error;
3156 }
3157 cur_arg++;
3158 on_success_msg = args[cur_arg];
3159 }
3160 else if (strcmp(args[cur_arg], "on-error") == 0) {
3161 if (in_pattern) {
3162 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3163 goto error;
3164 }
3165 if (!*(args[cur_arg+1])) {
3166 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3167 goto error;
3168 }
3169 cur_arg++;
3170 on_error_msg = args[cur_arg];
3171 }
3172 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3173 if (in_pattern) {
3174 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3175 goto error;
3176 }
3177 if (!*(args[cur_arg+1])) {
3178 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3179 goto error;
3180 }
3181 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3182 ok_st = HCHK_STATUS_L7OKD;
3183 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3184 ok_st = HCHK_STATUS_L7OKCD;
3185 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3186 ok_st = HCHK_STATUS_L6OK;
3187 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3188 ok_st = HCHK_STATUS_L4OK;
3189 else {
3190 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3191 args[cur_arg], args[cur_arg+1]);
3192 goto error;
3193 }
3194 cur_arg++;
3195 }
3196 else if (strcmp(args[cur_arg], "error-status") == 0) {
3197 if (in_pattern) {
3198 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3199 goto error;
3200 }
3201 if (!*(args[cur_arg+1])) {
3202 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3203 goto error;
3204 }
3205 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3206 err_st = HCHK_STATUS_L7RSP;
3207 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3208 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003209 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3210 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003211 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3212 err_st = HCHK_STATUS_L6RSP;
3213 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3214 err_st = HCHK_STATUS_L4CON;
3215 else {
3216 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3217 args[cur_arg], args[cur_arg+1]);
3218 goto error;
3219 }
3220 cur_arg++;
3221 }
3222 else if (strcmp(args[cur_arg], "status-code") == 0) {
3223 int idx = 0;
3224
3225 if (in_pattern) {
3226 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3227 goto error;
3228 }
3229 if (!*(args[cur_arg+1])) {
3230 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3231 goto error;
3232 }
3233
3234 cur_arg++;
3235 release_sample_expr(status_expr);
3236 px->conf.args.ctx = ARGC_SRV;
3237 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003238 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003239 if (!status_expr) {
3240 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3241 goto error;
3242 }
3243 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3244 memprintf(errmsg, "error detected while parsing status-code expression : "
3245 " fetch method '%s' extracts information from '%s', "
3246 "none of which is available here.\n",
3247 args[cur_arg], sample_src_names(status_expr->fetch->use));
3248 goto error;
3249 }
3250 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3251 }
3252 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3253 if (in_pattern) {
3254 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3255 goto error;
3256 }
3257 if (!*(args[cur_arg+1])) {
3258 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3259 goto error;
3260 }
3261 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3262 tout_st = HCHK_STATUS_L7TOUT;
3263 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3264 tout_st = HCHK_STATUS_L6TOUT;
3265 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3266 tout_st = HCHK_STATUS_L4TOUT;
3267 else {
3268 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3269 args[cur_arg], args[cur_arg+1]);
3270 goto error;
3271 }
3272 cur_arg++;
3273 }
3274 else {
3275 if (proto == TCPCHK_RULES_HTTP_CHK) {
3276 bad_http_kw:
3277 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3278 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3279 }
3280 else {
3281 bad_tcp_kw:
3282 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3283 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3284 }
3285 goto error;
3286 }
3287 next:
3288 cur_arg++;
3289 }
3290
3291 chk = calloc(1, sizeof(*chk));
3292 if (!chk) {
3293 memprintf(errmsg, "out of memory");
3294 goto error;
3295 }
3296 chk->action = TCPCHK_ACT_EXPECT;
3297 LIST_INIT(&chk->expect.onerror_fmt);
3298 LIST_INIT(&chk->expect.onsuccess_fmt);
3299 chk->comment = comment; comment = NULL;
3300 chk->expect.type = type;
3301 chk->expect.min_recv = min_recv;
3302 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3303 chk->expect.ok_status = ok_st;
3304 chk->expect.err_status = err_st;
3305 chk->expect.tout_status = tout_st;
3306 chk->expect.status_expr = status_expr; status_expr = NULL;
3307
3308 if (on_success_msg) {
3309 px->conf.args.ctx = ARGC_SRV;
3310 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3311 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3312 goto error;
3313 }
3314 }
3315 if (on_error_msg) {
3316 px->conf.args.ctx = ARGC_SRV;
3317 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3318 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3319 goto error;
3320 }
3321 }
3322
3323 switch (chk->expect.type) {
3324 case TCPCHK_EXPECT_HTTP_STATUS: {
3325 const char *p = pattern;
3326 unsigned int c1,c2;
3327
3328 chk->expect.codes.codes = NULL;
3329 chk->expect.codes.num = 0;
3330 while (1) {
3331 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3332 if (*p == '-') {
3333 p++;
3334 c2 = read_uint(&p, pattern + strlen(pattern));
3335 }
3336 if (c1 > c2) {
3337 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3338 goto error;
3339 }
3340
3341 chk->expect.codes.num++;
3342 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3343 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3344 if (!chk->expect.codes.codes) {
3345 memprintf(errmsg, "out of memory");
3346 goto error;
3347 }
3348 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3349 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3350
3351 if (*p == '\0')
3352 break;
3353 if (*p != ',') {
3354 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3355 goto error;
3356 }
3357 p++;
3358 }
3359 break;
3360 }
3361 case TCPCHK_EXPECT_STRING:
3362 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003363 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003364 if (!isttest(chk->expect.data)) {
3365 memprintf(errmsg, "out of memory");
3366 goto error;
3367 }
3368 break;
3369 case TCPCHK_EXPECT_BINARY: {
3370 int len = chk->expect.data.len;
3371
3372 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3373 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3374 goto error;
3375 }
3376 chk->expect.data.len = len;
3377 break;
3378 }
3379 case TCPCHK_EXPECT_STRING_REGEX:
3380 case TCPCHK_EXPECT_BINARY_REGEX:
3381 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3382 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3383 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3384 if (!chk->expect.regex)
3385 goto error;
3386 break;
3387
3388 case TCPCHK_EXPECT_STRING_LF:
3389 case TCPCHK_EXPECT_BINARY_LF:
3390 case TCPCHK_EXPECT_HTTP_BODY_LF:
3391 LIST_INIT(&chk->expect.fmt);
3392 px->conf.args.ctx = ARGC_SRV;
3393 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3394 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3395 goto error;
3396 }
3397 break;
3398
3399 case TCPCHK_EXPECT_HTTP_HEADER:
3400 if (!npat) {
3401 memprintf(errmsg, "unexpected error, undefined header name pattern");
3402 goto error;
3403 }
3404 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3405 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3406 if (!chk->expect.hdr.name_re)
3407 goto error;
3408 }
3409 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3410 px->conf.args.ctx = ARGC_SRV;
3411 LIST_INIT(&chk->expect.hdr.name_fmt);
3412 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3413 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3414 goto error;
3415 }
3416 }
3417 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003418 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003419 if (!isttest(chk->expect.hdr.name)) {
3420 memprintf(errmsg, "out of memory");
3421 goto error;
3422 }
3423 }
3424
3425 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3426 chk->expect.hdr.value = IST_NULL;
3427 break;
3428 }
3429
3430 if (!vpat) {
3431 memprintf(errmsg, "unexpected error, undefined header value pattern");
3432 goto error;
3433 }
3434 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3435 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3436 if (!chk->expect.hdr.value_re)
3437 goto error;
3438 }
3439 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3440 px->conf.args.ctx = ARGC_SRV;
3441 LIST_INIT(&chk->expect.hdr.value_fmt);
3442 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3443 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3444 goto error;
3445 }
3446 }
3447 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003448 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003449 if (!isttest(chk->expect.hdr.value)) {
3450 memprintf(errmsg, "out of memory");
3451 goto error;
3452 }
3453 }
3454
3455 break;
3456 case TCPCHK_EXPECT_CUSTOM:
3457 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3458 break;
3459 case TCPCHK_EXPECT_UNDEF:
3460 memprintf(errmsg, "pattern not found");
3461 goto error;
3462 }
3463
3464 /* All tcp-check expect points back to the first inverse expect rule in
3465 * a chain of one or more expect rule, potentially itself.
3466 */
3467 chk->expect.head = chk;
3468 list_for_each_entry_rev(prev_check, rules, list) {
3469 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3470 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3471 chk->expect.head = prev_check;
3472 continue;
3473 }
3474 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3475 break;
3476 }
3477 return chk;
3478
3479 error:
3480 free_tcpcheck(chk, 0);
3481 free(comment);
3482 release_sample_expr(status_expr);
3483 return NULL;
3484}
3485
3486/* Overwrites fields of the old http send rule with those of the new one. When
3487 * replaced, old values are freed and replaced by the new ones. New values are
3488 * not copied but transferred. At the end <new> should be empty and can be
3489 * safely released. This function never fails.
3490 */
3491void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3492{
3493 struct logformat_node *lf, *lfb;
3494 struct tcpcheck_http_hdr *hdr, *bhdr;
3495
3496
3497 if (new->send.http.meth.str.area) {
3498 free(old->send.http.meth.str.area);
3499 old->send.http.meth.meth = new->send.http.meth.meth;
3500 old->send.http.meth.str.area = new->send.http.meth.str.area;
3501 old->send.http.meth.str.data = new->send.http.meth.str.data;
3502 new->send.http.meth.str = BUF_NULL;
3503 }
3504
3505 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3506 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3507 istfree(&old->send.http.uri);
3508 else
3509 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3510 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3511 old->send.http.uri = new->send.http.uri;
3512 new->send.http.uri = IST_NULL;
3513 }
3514 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3515 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3516 istfree(&old->send.http.uri);
3517 else
3518 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3519 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3520 LIST_INIT(&old->send.http.uri_fmt);
3521 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003522 LIST_DELETE(&lf->list);
3523 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003524 }
3525 }
3526
3527 if (isttest(new->send.http.vsn)) {
3528 istfree(&old->send.http.vsn);
3529 old->send.http.vsn = new->send.http.vsn;
3530 new->send.http.vsn = IST_NULL;
3531 }
3532
Christopher Faulet4c8e58d2022-07-05 15:33:53 +02003533 if (!LIST_ISEMPTY(&new->send.http.hdrs)) {
3534 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3535 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
3536 LIST_DELETE(&hdr->list);
3537 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
3538 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02003539 }
3540
3541 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3542 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3543 istfree(&old->send.http.body);
3544 else
3545 free_tcpcheck_fmt(&old->send.http.body_fmt);
3546 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3547 old->send.http.body = new->send.http.body;
3548 new->send.http.body = IST_NULL;
3549 }
3550 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3551 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3552 istfree(&old->send.http.body);
3553 else
3554 free_tcpcheck_fmt(&old->send.http.body_fmt);
3555 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3556 LIST_INIT(&old->send.http.body_fmt);
3557 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003558 LIST_DELETE(&lf->list);
3559 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003560 }
3561 }
3562}
3563
3564/* Internal function used to add an http-check rule in a list during the config
3565 * parsing step. Depending on its type, and the previously inserted rules, a
3566 * specific action may be performed or an error may be reported. This functions
3567 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3568 * message.
3569 */
3570int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3571{
3572 struct tcpcheck_rule *r;
3573
3574 /* the implicit send rule coming from an "option httpchk" line must be
3575 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003576 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003577 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003578 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003579 * sure the ruleset remains valid.
3580 */
3581
3582 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3583 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3584 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3585 * following tests are performed :
3586 *
3587 * 1- If there is no such rule or if it is not a send rule, the implicit send
3588 * rule is pushed in front of the ruleset
3589 *
3590 * 2- If it is another implicit send rule, it is replaced with the new one.
3591 *
3592 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3593 * both, overwriting the old send rule (the explicit one) with info of the
3594 * new send rule (the implicit one).
3595 */
3596 r = get_first_tcpcheck_rule(rules);
3597 if (r && r->action == TCPCHK_ACT_CONNECT)
3598 r = get_next_tcpcheck_rule(rules, r);
3599 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003600 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003601 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003602 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003603 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003604 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003605 }
3606 else {
3607 tcpcheck_overwrite_send_http_rule(r, chk);
3608 free_tcpcheck(chk, 0);
3609 }
3610 }
3611 else {
3612 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3613 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3614 * with an existing implicit send rule, if any. At the end, if there is no error,
3615 * the rule is appended to the list.
3616 */
3617
3618 r = get_last_tcpcheck_rule(rules);
3619 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3620 /* no error */;
3621 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3622 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3623 chk->index+1);
3624 return 0;
3625 }
3626 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3627 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3628 chk->index+1);
3629 return 0;
3630 }
3631 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3632 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3633 chk->index+1);
3634 return 0;
3635 }
3636
3637 if (chk->action == TCPCHK_ACT_SEND) {
3638 r = get_first_tcpcheck_rule(rules);
3639 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3640 tcpcheck_overwrite_send_http_rule(r, chk);
3641 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003642 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003643 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3644 chk = r;
3645 }
3646 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003647 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003648 }
3649 return 1;
3650}
3651
3652/* Check tcp-check health-check configuration for the proxy <px>. */
3653static int check_proxy_tcpcheck(struct proxy *px)
3654{
3655 struct tcpcheck_rule *chk, *back;
3656 char *comment = NULL, *errmsg = NULL;
3657 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003658 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003659
3660 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3661 deinit_proxy_tcpcheck(px);
3662 goto out;
3663 }
3664
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003665 ha_free(&px->check_command);
3666 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003667
3668 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003669 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003670 ret |= ERR_ALERT | ERR_FATAL;
3671 goto out;
3672 }
3673
3674 /* HTTP ruleset only : */
3675 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3676 struct tcpcheck_rule *next;
3677
3678 /* move remaining implicit send rule from "option httpchk" line to the right place.
3679 * If such rule exists, it must be the first one. In this case, the rule is moved
3680 * after the first connect rule, if any. Otherwise, nothing is done.
3681 */
3682 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3683 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3684 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3685 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003686 LIST_DELETE(&chk->list);
3687 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003688 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003689 }
3690 }
3691
3692 /* add implicit expect rule if the last one is a send. It is inherited from previous
3693 * versions where the http expect rule was optional. Now it is possible to chained
3694 * send/expect rules but the last expect may still be implicit.
3695 */
3696 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3697 if (chk && chk->action == TCPCHK_ACT_SEND) {
3698 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3699 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3700 px->conf.file, px->conf.line, &errmsg);
3701 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003702 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003703 "(%s).\n", px->id, errmsg);
3704 free(errmsg);
3705 ret |= ERR_ALERT | ERR_FATAL;
3706 goto out;
3707 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003708 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003709 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003710 }
3711 }
3712
3713 /* For all ruleset: */
3714
3715 /* If there is no connect rule preceding all send / expect rules, an
3716 * implicit one is inserted before all others.
3717 */
3718 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3719 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3720 chk = calloc(1, sizeof(*chk));
3721 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003722 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003723 "(out of memory).\n", px->id);
3724 ret |= ERR_ALERT | ERR_FATAL;
3725 goto out;
3726 }
3727 chk->action = TCPCHK_ACT_CONNECT;
3728 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003729 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003730 }
3731
3732 /* Remove all comment rules. To do so, when a such rule is found, the
3733 * comment is assigned to the following rule(s).
3734 */
3735 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Faulet871dd822022-08-24 11:38:03 +02003736 struct tcpcheck_rule *next;
3737
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003738 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3739 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003740
3741 prev_action = chk->action;
3742 switch (chk->action) {
3743 case TCPCHK_ACT_COMMENT:
3744 free(comment);
3745 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003746 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003747 free(chk);
3748 break;
3749 case TCPCHK_ACT_CONNECT:
3750 if (!chk->comment && comment)
3751 chk->comment = strdup(comment);
Christopher Faulet871dd822022-08-24 11:38:03 +02003752 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3753 if (next && next->action == TCPCHK_ACT_SEND)
3754 chk->connect.options |= TCPCHK_OPT_HAS_DATA;
Willy Tarreauf3f60762022-11-14 07:10:33 +01003755 __fallthrough;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003756 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003757 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003758 break;
3759 case TCPCHK_ACT_SEND:
3760 case TCPCHK_ACT_EXPECT:
3761 if (!chk->comment && comment)
3762 chk->comment = strdup(comment);
3763 break;
3764 }
3765 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003766 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003767
3768 out:
3769 return ret;
3770}
3771
3772void deinit_proxy_tcpcheck(struct proxy *px)
3773{
3774 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3775 px->tcpcheck_rules.flags = 0;
3776 px->tcpcheck_rules.list = NULL;
3777}
3778
3779static void deinit_tcpchecks()
3780{
3781 struct tcpcheck_ruleset *rs;
3782 struct tcpcheck_rule *r, *rb;
3783 struct ebpt_node *node, *next;
3784
3785 node = ebpt_first(&shared_tcpchecks);
3786 while (node) {
3787 next = ebpt_next(node);
3788 ebpt_delete(node);
3789 free(node->key);
3790 rs = container_of(node, typeof(*rs), node);
3791 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003792 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003793 free_tcpcheck(r, 0);
3794 }
3795 free(rs);
3796 node = next;
3797 }
3798}
3799
3800int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3801{
3802 struct tcpcheck_rule *tcpcheck, *prev_check;
3803 struct tcpcheck_expect *expect;
3804
Willy Tarreau6922e552021-03-22 21:11:45 +01003805 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003806 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003807 tcpcheck->action = TCPCHK_ACT_EXPECT;
3808
3809 expect = &tcpcheck->expect;
3810 expect->type = TCPCHK_EXPECT_STRING;
3811 LIST_INIT(&expect->onerror_fmt);
3812 LIST_INIT(&expect->onsuccess_fmt);
3813 expect->ok_status = HCHK_STATUS_L7OKD;
3814 expect->err_status = HCHK_STATUS_L7RSP;
3815 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003816 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003817 if (!isttest(expect->data)) {
3818 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3819 return 0;
3820 }
3821
3822 /* All tcp-check expect points back to the first inverse expect rule
3823 * in a chain of one or more expect rule, potentially itself.
3824 */
3825 tcpcheck->expect.head = tcpcheck;
3826 list_for_each_entry_rev(prev_check, rules->list, list) {
3827 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3828 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3829 tcpcheck->expect.head = prev_check;
3830 continue;
3831 }
3832 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3833 break;
3834 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003835 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003836 return 1;
3837}
3838
3839int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3840{
3841 struct tcpcheck_rule *tcpcheck;
3842 struct tcpcheck_send *send;
3843 const char *in;
3844 char *dst;
3845 int i;
3846
Willy Tarreau6922e552021-03-22 21:11:45 +01003847 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003848 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003849 tcpcheck->action = TCPCHK_ACT_SEND;
3850
3851 send = &tcpcheck->send;
3852 send->type = TCPCHK_SEND_STRING;
3853
3854 for (i = 0; strs[i]; i++)
3855 send->data.len += strlen(strs[i]);
3856
3857 send->data.ptr = malloc(istlen(send->data) + 1);
3858 if (!isttest(send->data)) {
3859 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3860 return 0;
3861 }
3862
3863 dst = istptr(send->data);
3864 for (i = 0; strs[i]; i++)
3865 for (in = strs[i]; (*dst = *in++); dst++);
3866 *dst = 0;
3867
Willy Tarreau2b718102021-04-21 07:32:39 +02003868 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003869 return 1;
3870}
3871
3872/* Parses the "tcp-check" proxy keyword */
3873static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003874 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003875 char **errmsg)
3876{
3877 struct tcpcheck_ruleset *rs = NULL;
3878 struct tcpcheck_rule *chk = NULL;
3879 int index, cur_arg, ret = 0;
3880
3881 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3882 ret = 1;
3883
3884 /* Deduce the ruleset name from the proxy info */
3885 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3886 ((curpx == defpx) ? "defaults" : curpx->id),
3887 curpx->conf.file, curpx->conf.line);
3888
3889 rs = find_tcpcheck_ruleset(b_orig(&trash));
3890 if (rs == NULL) {
3891 rs = create_tcpcheck_ruleset(b_orig(&trash));
3892 if (rs == NULL) {
3893 memprintf(errmsg, "out of memory.\n");
3894 goto error;
3895 }
3896 }
3897
3898 index = 0;
3899 if (!LIST_ISEMPTY(&rs->rules)) {
3900 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3901 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003902 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003903 }
3904
3905 cur_arg = 1;
3906 if (strcmp(args[cur_arg], "connect") == 0)
3907 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3908 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3909 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3910 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3911 else if (strcmp(args[cur_arg], "expect") == 0)
3912 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3913 else if (strcmp(args[cur_arg], "comment") == 0)
3914 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3915 else {
3916 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3917
3918 if (!kw) {
3919 action_kw_tcp_check_build_list(&trash);
3920 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3921 "%s%s. but got '%s'",
3922 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3923 goto error;
3924 }
3925 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3926 }
3927
3928 if (!chk) {
3929 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3930 goto error;
3931 }
3932 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3933
3934 /* No error: add the tcp-check rule in the list */
3935 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003936 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003937
3938 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3939 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3940 /* Use this ruleset if the proxy already has tcp-check enabled */
3941 curpx->tcpcheck_rules.list = &rs->rules;
3942 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3943 }
3944 else {
3945 /* mark this ruleset as unused for now */
3946 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3947 }
3948
3949 return ret;
3950
3951 error:
3952 free_tcpcheck(chk, 0);
3953 free_tcpcheck_ruleset(rs);
3954 return -1;
3955}
3956
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003957/* Parses the "http-check" proxy keyword */
3958static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003959 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003960 char **errmsg)
3961{
3962 struct tcpcheck_ruleset *rs = NULL;
3963 struct tcpcheck_rule *chk = NULL;
3964 int index, cur_arg, ret = 0;
3965
3966 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3967 ret = 1;
3968
3969 cur_arg = 1;
3970 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3971 /* enable a graceful server shutdown on an HTTP 404 response */
3972 curpx->options |= PR_O_DISABLE404;
3973 if (too_many_args(1, args, errmsg, NULL))
3974 goto error;
3975 goto out;
3976 }
3977 else if (strcmp(args[cur_arg], "send-state") == 0) {
3978 /* enable emission of the apparent state of a server in HTTP checks */
3979 curpx->options2 |= PR_O2_CHK_SNDST;
3980 if (too_many_args(1, args, errmsg, NULL))
3981 goto error;
3982 goto out;
3983 }
3984
3985 /* Deduce the ruleset name from the proxy info */
3986 chunk_printf(&trash, "*http-check-%s_%s-%d",
3987 ((curpx == defpx) ? "defaults" : curpx->id),
3988 curpx->conf.file, curpx->conf.line);
3989
3990 rs = find_tcpcheck_ruleset(b_orig(&trash));
3991 if (rs == NULL) {
3992 rs = create_tcpcheck_ruleset(b_orig(&trash));
3993 if (rs == NULL) {
3994 memprintf(errmsg, "out of memory.\n");
3995 goto error;
3996 }
3997 }
3998
3999 index = 0;
4000 if (!LIST_ISEMPTY(&rs->rules)) {
4001 chk = LIST_PREV(&rs->rules, typeof(chk), list);
4002 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4003 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004004 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004005 }
4006
4007 if (strcmp(args[cur_arg], "connect") == 0)
4008 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4009 else if (strcmp(args[cur_arg], "send") == 0)
4010 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4011 else if (strcmp(args[cur_arg], "expect") == 0)
4012 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4013 file, line, errmsg);
4014 else if (strcmp(args[cur_arg], "comment") == 0)
4015 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4016 else {
4017 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4018
4019 if (!kw) {
4020 action_kw_tcp_check_build_list(&trash);
4021 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4022 " 'send', 'expect'%s%s. but got '%s'",
4023 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4024 goto error;
4025 }
4026 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4027 }
4028
4029 if (!chk) {
4030 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4031 goto error;
4032 }
4033 ret = (*errmsg != NULL); /* Handle warning */
4034
4035 chk->index = index;
4036 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4037 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4038 /* Use this ruleset if the proxy already has http-check enabled */
4039 curpx->tcpcheck_rules.list = &rs->rules;
4040 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4041 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4042 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4043 curpx->tcpcheck_rules.list = NULL;
4044 goto error;
4045 }
4046 }
4047 else {
4048 /* mark this ruleset as unused for now */
4049 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004050 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004051 }
4052
4053 out:
4054 return ret;
4055
4056 error:
4057 free_tcpcheck(chk, 0);
4058 free_tcpcheck_ruleset(rs);
4059 return -1;
4060}
4061
4062/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004063int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004064 const char *file, int line)
4065{
4066 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4067 static char *redis_res = "+PONG\r\n";
4068
4069 struct tcpcheck_ruleset *rs = NULL;
4070 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4071 struct tcpcheck_rule *chk;
4072 char *errmsg = NULL;
4073 int err_code = 0;
4074
4075 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4076 err_code |= ERR_WARN;
4077
4078 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4079 goto out;
4080
4081 curpx->options2 &= ~PR_O2_CHK_ANY;
4082 curpx->options2 |= PR_O2_TCPCHK_CHK;
4083
4084 free_tcpcheck_vars(&rules->preset_vars);
4085 rules->list = NULL;
4086 rules->flags = 0;
4087
4088 rs = find_tcpcheck_ruleset("*redis-check");
4089 if (rs)
4090 goto ruleset_found;
4091
4092 rs = create_tcpcheck_ruleset("*redis-check");
4093 if (rs == NULL) {
4094 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4095 goto error;
4096 }
4097
4098 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4099 1, curpx, &rs->rules, file, line, &errmsg);
4100 if (!chk) {
4101 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4102 goto error;
4103 }
4104 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004105 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004106
4107 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4108 "error-status", "L7STS",
4109 "on-error", "%[res.payload(0,0),cut_crlf]",
4110 "on-success", "Redis server is ok",
4111 ""},
4112 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4113 if (!chk) {
4114 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4115 goto error;
4116 }
4117 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004118 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004119
4120 ruleset_found:
4121 rules->list = &rs->rules;
4122 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4123 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4124
4125 out:
4126 free(errmsg);
4127 return err_code;
4128
4129 error:
4130 free_tcpcheck_ruleset(rs);
4131 err_code |= ERR_ALERT | ERR_FATAL;
4132 goto out;
4133}
4134
4135
4136/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004137int 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 +01004138 const char *file, int line)
4139{
4140 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4141 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4142 *
4143 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4144 */
4145 static char sslv3_client_hello[] = {
4146 "16" /* ContentType : 0x16 = Handshake */
4147 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4148 "0079" /* ContentLength : 0x79 bytes after this one */
4149 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4150 "000075" /* HandshakeLength : 0x75 bytes after this one */
4151 "0300" /* Hello Version : 0x0300 = v3 */
4152 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4153 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4154 "00" /* Session ID length : empty (no session ID) */
4155 "004E" /* Cipher Suite Length : 78 bytes after this one */
4156 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4157 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4158 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4159 "000D" "000E" "000F" "0010" /* various bit lengths, */
4160 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4161 "0015" "0016" "0017" "0018"
4162 "0019" "001A" "001B" "002F"
4163 "0030" "0031" "0032" "0033"
4164 "0034" "0035" "0036" "0037"
4165 "0038" "0039" "003A"
4166 "01" /* Compression Length : 0x01 = 1 byte for types */
4167 "00" /* Compression Type : 0x00 = NULL compression */
4168 };
4169
4170 struct tcpcheck_ruleset *rs = NULL;
4171 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4172 struct tcpcheck_rule *chk;
4173 char *errmsg = NULL;
4174 int err_code = 0;
4175
4176 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4177 err_code |= ERR_WARN;
4178
4179 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4180 goto out;
4181
4182 curpx->options2 &= ~PR_O2_CHK_ANY;
4183 curpx->options2 |= PR_O2_TCPCHK_CHK;
4184
4185 free_tcpcheck_vars(&rules->preset_vars);
4186 rules->list = NULL;
4187 rules->flags = 0;
4188
4189 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4190 if (rs)
4191 goto ruleset_found;
4192
4193 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4194 if (rs == NULL) {
4195 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4196 goto error;
4197 }
4198
4199 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4200 1, curpx, &rs->rules, file, line, &errmsg);
4201 if (!chk) {
4202 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4203 goto error;
4204 }
4205 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004206 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004207
4208 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4209 "min-recv", "5", "ok-status", "L6OK",
4210 "error-status", "L6RSP", "tout-status", "L6TOUT",
4211 ""},
4212 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4213 if (!chk) {
4214 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4215 goto error;
4216 }
4217 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004218 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004219
4220 ruleset_found:
4221 rules->list = &rs->rules;
4222 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4223 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4224
4225 out:
4226 free(errmsg);
4227 return err_code;
4228
4229 error:
4230 free_tcpcheck_ruleset(rs);
4231 err_code |= ERR_ALERT | ERR_FATAL;
4232 goto out;
4233}
4234
4235/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004236int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004237 const char *file, int line)
4238{
4239 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4240
4241 struct tcpcheck_ruleset *rs = NULL;
4242 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4243 struct tcpcheck_rule *chk;
4244 struct tcpcheck_var *var = NULL;
4245 char *cmd = NULL, *errmsg = NULL;
4246 int err_code = 0;
4247
4248 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4249 err_code |= ERR_WARN;
4250
4251 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4252 goto out;
4253
4254 curpx->options2 &= ~PR_O2_CHK_ANY;
4255 curpx->options2 |= PR_O2_TCPCHK_CHK;
4256
4257 free_tcpcheck_vars(&rules->preset_vars);
4258 rules->list = NULL;
4259 rules->flags = 0;
4260
4261 cur_arg += 2;
4262 if (*args[cur_arg] && *args[cur_arg+1] &&
4263 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4264 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4265 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4266 if (cmd)
4267 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4268 }
4269 else {
4270 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4271 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4272 cmd = strdup("HELO localhost");
4273 }
4274
4275 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4276 if (cmd == NULL || var == NULL) {
4277 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4278 goto error;
4279 }
4280 var->data.type = SMP_T_STR;
4281 var->data.u.str.area = cmd;
4282 var->data.u.str.data = strlen(cmd);
4283 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004284 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004285 cmd = NULL;
4286 var = NULL;
4287
4288 rs = find_tcpcheck_ruleset("*smtp-check");
4289 if (rs)
4290 goto ruleset_found;
4291
4292 rs = create_tcpcheck_ruleset("*smtp-check");
4293 if (rs == NULL) {
4294 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4295 goto error;
4296 }
4297
4298 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4299 1, curpx, &rs->rules, file, line, &errmsg);
4300 if (!chk) {
4301 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4302 goto error;
4303 }
4304 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004305 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004306
4307 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4308 "min-recv", "4",
4309 "error-status", "L7RSP",
4310 "on-error", "%[res.payload(0,0),cut_crlf]",
4311 ""},
4312 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4313 if (!chk) {
4314 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4315 goto error;
4316 }
4317 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004318 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004319
4320 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4321 "min-recv", "4",
4322 "error-status", "L7STS",
4323 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4324 "status-code", "res.payload(0,3)",
4325 ""},
4326 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4327 if (!chk) {
4328 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4329 goto error;
4330 }
4331 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004332 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004333
4334 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4335 1, curpx, &rs->rules, file, line, &errmsg);
4336 if (!chk) {
4337 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4338 goto error;
4339 }
4340 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004341 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004342
Christopher Faulet2ec1ffa2022-09-21 14:42:47 +02004343 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 +01004344 "error-status", "L7STS",
4345 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4346 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4347 "status-code", "res.payload(0,3)",
4348 ""},
4349 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4350 if (!chk) {
4351 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4352 goto error;
4353 }
4354 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004355 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004356
wrightlaw9a8d8a32022-09-08 16:10:48 +01004357 /* Send an SMTP QUIT to ensure clean disconnect (issue 1812), and expect a 2xx response code */
4358
4359 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "QUIT\r\n", ""},
4360 1, curpx, &rs->rules, file, line, &errmsg);
4361 if (!chk) {
4362 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4363 goto error;
4364 }
4365 chk->index = 5;
4366 LIST_APPEND(&rs->rules, &chk->list);
4367
4368 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4369 "min-recv", "4",
4370 "error-status", "L7STS",
4371 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4372 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4373 "status-code", "res.payload(0,3)",
4374 ""},
4375 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4376 if (!chk) {
4377 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4378 goto error;
4379 }
4380 chk->index = 6;
4381 LIST_APPEND(&rs->rules, &chk->list);
4382
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004383 ruleset_found:
4384 rules->list = &rs->rules;
4385 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4386 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4387
4388 out:
4389 free(errmsg);
4390 return err_code;
4391
4392 error:
4393 free(cmd);
4394 free(var);
4395 free_tcpcheck_vars(&rules->preset_vars);
4396 free_tcpcheck_ruleset(rs);
4397 err_code |= ERR_ALERT | ERR_FATAL;
4398 goto out;
4399}
4400
4401/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004402int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004403 const char *file, int line)
4404{
4405 static char pgsql_req[] = {
4406 "%[var(check.plen),htonl,hex]" /* The packet length*/
4407 "00030000" /* the version 3.0 */
4408 "7573657200" /* "user" key */
4409 "%[var(check.username),hex]00" /* the username */
4410 "00"
4411 };
4412
4413 struct tcpcheck_ruleset *rs = NULL;
4414 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4415 struct tcpcheck_rule *chk;
4416 struct tcpcheck_var *var = NULL;
4417 char *user = NULL, *errmsg = NULL;
4418 size_t packetlen = 0;
4419 int err_code = 0;
4420
4421 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4422 err_code |= ERR_WARN;
4423
4424 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4425 goto out;
4426
4427 curpx->options2 &= ~PR_O2_CHK_ANY;
4428 curpx->options2 |= PR_O2_TCPCHK_CHK;
4429
4430 free_tcpcheck_vars(&rules->preset_vars);
4431 rules->list = NULL;
4432 rules->flags = 0;
4433
4434 cur_arg += 2;
4435 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4436 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4437 file, line, args[0], args[1]);
4438 goto error;
4439 }
4440 if (strcmp(args[cur_arg], "user") == 0) {
4441 packetlen = 15 + strlen(args[cur_arg+1]);
4442 user = strdup(args[cur_arg+1]);
4443
4444 var = create_tcpcheck_var(ist("check.username"));
4445 if (user == NULL || var == NULL) {
4446 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4447 goto error;
4448 }
4449 var->data.type = SMP_T_STR;
4450 var->data.u.str.area = user;
4451 var->data.u.str.data = strlen(user);
4452 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004453 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004454 user = NULL;
4455 var = NULL;
4456
4457 var = create_tcpcheck_var(ist("check.plen"));
4458 if (var == NULL) {
4459 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4460 goto error;
4461 }
4462 var->data.type = SMP_T_SINT;
4463 var->data.u.sint = packetlen;
4464 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004465 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004466 var = NULL;
4467 }
4468 else {
4469 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4470 file, line, args[0], args[1]);
4471 goto error;
4472 }
4473
4474 rs = find_tcpcheck_ruleset("*pgsql-check");
4475 if (rs)
4476 goto ruleset_found;
4477
4478 rs = create_tcpcheck_ruleset("*pgsql-check");
4479 if (rs == NULL) {
4480 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4481 goto error;
4482 }
4483
4484 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4485 1, curpx, &rs->rules, file, line, &errmsg);
4486 if (!chk) {
4487 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4488 goto error;
4489 }
4490 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004491 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004492
4493 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4494 1, curpx, &rs->rules, file, line, &errmsg);
4495 if (!chk) {
4496 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4497 goto error;
4498 }
4499 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004500 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004501
4502 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4503 "min-recv", "5",
4504 "error-status", "L7RSP",
4505 "on-error", "%[res.payload(6,0)]",
4506 ""},
4507 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4508 if (!chk) {
4509 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4510 goto error;
4511 }
4512 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004513 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004514
Fatih Acar0d6fb7a2022-09-26 17:27:11 +02004515 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 +01004516 "min-recv", "9",
4517 "error-status", "L7STS",
4518 "on-success", "PostgreSQL server is ok",
4519 "on-error", "PostgreSQL unknown error",
4520 ""},
4521 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4522 if (!chk) {
4523 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4524 goto error;
4525 }
4526 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004527 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004528
4529 ruleset_found:
4530 rules->list = &rs->rules;
4531 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4532 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4533
4534 out:
4535 free(errmsg);
4536 return err_code;
4537
4538 error:
4539 free(user);
4540 free(var);
4541 free_tcpcheck_vars(&rules->preset_vars);
4542 free_tcpcheck_ruleset(rs);
4543 err_code |= ERR_ALERT | ERR_FATAL;
4544 goto out;
4545}
4546
4547
4548/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004549int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004550 const char *file, int line)
4551{
4552 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4553 * const char mysql40_client_auth_pkt[] = {
4554 * "\x0e\x00\x00" // packet length
4555 * "\x01" // packet number
4556 * "\x00\x00" // client capabilities
4557 * "\x00\x00\x01" // max packet
4558 * "haproxy\x00" // username (null terminated string)
4559 * "\x00" // filler (always 0x00)
4560 * "\x01\x00\x00" // packet length
4561 * "\x00" // packet number
4562 * "\x01" // COM_QUIT command
4563 * };
4564 */
4565 static char mysql40_rsname[] = "*mysql40-check";
4566 static char mysql40_req[] = {
4567 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4568 "0080" /* client capabilities */
4569 "000001" /* max packet */
4570 "%[var(check.username),hex]00" /* the username */
4571 "00" /* filler (always 0x00) */
4572 "010000" /* packet length*/
4573 "00" /* sequence ID */
4574 "01" /* COM_QUIT command */
4575 };
4576
4577 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4578 * const char mysql41_client_auth_pkt[] = {
4579 * "\x0e\x00\x00\" // packet length
4580 * "\x01" // packet number
4581 * "\x00\x00\x00\x00" // client capabilities
4582 * "\x00\x00\x00\x01" // max packet
4583 * "\x21" // character set (UTF-8)
4584 * char[23] // All zeroes
4585 * "haproxy\x00" // username (null terminated string)
4586 * "\x00" // filler (always 0x00)
4587 * "\x01\x00\x00" // packet length
4588 * "\x00" // packet number
4589 * "\x01" // COM_QUIT command
4590 * };
4591 */
4592 static char mysql41_rsname[] = "*mysql41-check";
4593 static char mysql41_req[] = {
4594 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4595 "00820000" /* client capabilities */
4596 "00800001" /* max packet */
4597 "21" /* character set (UTF-8) */
4598 "000000000000000000000000" /* 23 bytes, al zeroes */
4599 "0000000000000000000000"
4600 "%[var(check.username),hex]00" /* the username */
4601 "00" /* filler (always 0x00) */
4602 "010000" /* packet length*/
4603 "00" /* sequence ID */
4604 "01" /* COM_QUIT command */
4605 };
4606
4607 struct tcpcheck_ruleset *rs = NULL;
4608 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4609 struct tcpcheck_rule *chk;
4610 struct tcpcheck_var *var = NULL;
4611 char *mysql_rsname = "*mysql-check";
4612 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4613 int index = 0, err_code = 0;
4614
4615 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4616 err_code |= ERR_WARN;
4617
4618 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4619 goto out;
4620
4621 curpx->options2 &= ~PR_O2_CHK_ANY;
4622 curpx->options2 |= PR_O2_TCPCHK_CHK;
4623
4624 free_tcpcheck_vars(&rules->preset_vars);
4625 rules->list = NULL;
4626 rules->flags = 0;
4627
4628 cur_arg += 2;
4629 if (*args[cur_arg]) {
4630 int packetlen, userlen;
4631
4632 if (strcmp(args[cur_arg], "user") != 0) {
4633 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4634 file, line, args[0], args[1], args[cur_arg]);
4635 goto error;
4636 }
4637
4638 if (*(args[cur_arg+1]) == 0) {
4639 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4640 file, line, args[0], args[1], args[cur_arg]);
4641 goto error;
4642 }
4643
4644 hdr = calloc(4, sizeof(*hdr));
4645 user = strdup(args[cur_arg+1]);
4646 userlen = strlen(args[cur_arg+1]);
4647
4648 if (hdr == NULL || user == NULL) {
4649 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4650 goto error;
4651 }
4652
4653 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4654 packetlen = userlen + 7 + 27;
4655 mysql_req = mysql41_req;
4656 mysql_rsname = mysql41_rsname;
4657 }
4658 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4659 packetlen = userlen + 7;
4660 mysql_req = mysql40_req;
4661 mysql_rsname = mysql40_rsname;
4662 }
4663 else {
4664 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4665 file, line, args[cur_arg], args[cur_arg+2]);
4666 goto error;
4667 }
4668
4669 hdr[0] = (unsigned char)(packetlen & 0xff);
4670 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4671 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4672 hdr[3] = 1;
4673
4674 var = create_tcpcheck_var(ist("check.header"));
4675 if (var == NULL) {
4676 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4677 goto error;
4678 }
4679 var->data.type = SMP_T_STR;
4680 var->data.u.str.area = hdr;
4681 var->data.u.str.data = 4;
4682 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004683 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004684 hdr = NULL;
4685 var = NULL;
4686
4687 var = create_tcpcheck_var(ist("check.username"));
4688 if (var == NULL) {
4689 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4690 goto error;
4691 }
4692 var->data.type = SMP_T_STR;
4693 var->data.u.str.area = user;
4694 var->data.u.str.data = strlen(user);
4695 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004696 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004697 user = NULL;
4698 var = NULL;
4699 }
4700
4701 rs = find_tcpcheck_ruleset(mysql_rsname);
4702 if (rs)
4703 goto ruleset_found;
4704
4705 rs = create_tcpcheck_ruleset(mysql_rsname);
4706 if (rs == NULL) {
4707 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4708 goto error;
4709 }
4710
4711 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4712 1, curpx, &rs->rules, file, line, &errmsg);
4713 if (!chk) {
4714 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4715 goto error;
4716 }
4717 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004718 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004719
4720 if (mysql_req) {
4721 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4722 1, curpx, &rs->rules, file, line, &errmsg);
4723 if (!chk) {
4724 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4725 goto error;
4726 }
4727 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004728 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004729 }
4730
4731 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4732 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4733 if (!chk) {
4734 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4735 goto error;
4736 }
4737 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4738 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004739 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004740
4741 if (mysql_req) {
4742 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4743 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4744 if (!chk) {
4745 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4746 goto error;
4747 }
4748 chk->expect.custom = tcpcheck_mysql_expect_ok;
4749 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004750 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004751 }
4752
4753 ruleset_found:
4754 rules->list = &rs->rules;
4755 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4756 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4757
4758 out:
4759 free(errmsg);
4760 return err_code;
4761
4762 error:
4763 free(hdr);
4764 free(user);
4765 free(var);
4766 free_tcpcheck_vars(&rules->preset_vars);
4767 free_tcpcheck_ruleset(rs);
4768 err_code |= ERR_ALERT | ERR_FATAL;
4769 goto out;
4770}
4771
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004772int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004773 const char *file, int line)
4774{
4775 static char *ldap_req = "300C020101600702010304008000";
4776
4777 struct tcpcheck_ruleset *rs = NULL;
4778 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4779 struct tcpcheck_rule *chk;
4780 char *errmsg = NULL;
4781 int err_code = 0;
4782
4783 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4784 err_code |= ERR_WARN;
4785
4786 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4787 goto out;
4788
4789 curpx->options2 &= ~PR_O2_CHK_ANY;
4790 curpx->options2 |= PR_O2_TCPCHK_CHK;
4791
4792 free_tcpcheck_vars(&rules->preset_vars);
4793 rules->list = NULL;
4794 rules->flags = 0;
4795
4796 rs = find_tcpcheck_ruleset("*ldap-check");
4797 if (rs)
4798 goto ruleset_found;
4799
4800 rs = create_tcpcheck_ruleset("*ldap-check");
4801 if (rs == NULL) {
4802 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4803 goto error;
4804 }
4805
4806 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4807 1, curpx, &rs->rules, file, line, &errmsg);
4808 if (!chk) {
4809 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4810 goto error;
4811 }
4812 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004813 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004814
4815 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4816 "min-recv", "14",
4817 "on-error", "Not LDAPv3 protocol",
4818 ""},
4819 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4820 if (!chk) {
4821 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4822 goto error;
4823 }
4824 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004825 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004826
4827 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4828 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4829 if (!chk) {
4830 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4831 goto error;
4832 }
4833 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4834 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004835 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004836
4837 ruleset_found:
4838 rules->list = &rs->rules;
4839 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4840 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4841
4842 out:
4843 free(errmsg);
4844 return err_code;
4845
4846 error:
4847 free_tcpcheck_ruleset(rs);
4848 err_code |= ERR_ALERT | ERR_FATAL;
4849 goto out;
4850}
4851
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004852int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004853 const char *file, int line)
4854{
4855 struct tcpcheck_ruleset *rs = NULL;
4856 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4857 struct tcpcheck_rule *chk;
4858 char *spop_req = NULL;
4859 char *errmsg = NULL;
4860 int spop_len = 0, err_code = 0;
4861
4862 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4863 err_code |= ERR_WARN;
4864
4865 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4866 goto out;
4867
4868 curpx->options2 &= ~PR_O2_CHK_ANY;
4869 curpx->options2 |= PR_O2_TCPCHK_CHK;
4870
4871 free_tcpcheck_vars(&rules->preset_vars);
4872 rules->list = NULL;
4873 rules->flags = 0;
4874
4875
4876 rs = find_tcpcheck_ruleset("*spop-check");
4877 if (rs)
4878 goto ruleset_found;
4879
4880 rs = create_tcpcheck_ruleset("*spop-check");
4881 if (rs == NULL) {
4882 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4883 goto error;
4884 }
4885
4886 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4887 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4888 goto error;
4889 }
4890 chunk_reset(&trash);
4891 dump_binary(&trash, spop_req, spop_len);
4892 trash.area[trash.data] = '\0';
4893
4894 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4895 1, curpx, &rs->rules, file, line, &errmsg);
4896 if (!chk) {
4897 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4898 goto error;
4899 }
4900 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004901 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004902
4903 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4904 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4905 if (!chk) {
4906 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4907 goto error;
4908 }
4909 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4910 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004911 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004912
4913 ruleset_found:
4914 rules->list = &rs->rules;
4915 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4916 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4917
4918 out:
4919 free(spop_req);
4920 free(errmsg);
4921 return err_code;
4922
4923 error:
4924 free_tcpcheck_ruleset(rs);
4925 err_code |= ERR_ALERT | ERR_FATAL;
4926 goto out;
4927}
4928
4929
4930static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4931{
4932 struct tcpcheck_rule *chk = NULL;
4933 struct tcpcheck_http_hdr *hdr = NULL;
4934 char *meth = NULL, *uri = NULL, *vsn = NULL;
4935 char *hdrs, *body;
4936
4937 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4938 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004939 if (hdrs || body) {
Christopher Faulet4b5f3022022-09-05 09:05:17 +02004940 memprintf(errmsg, "hiding headers or body at the end of the version string is unsupported."
4941 "Use 'http-check send' directive instead.");
4942 goto error;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004943 }
4944
4945 chk = calloc(1, sizeof(*chk));
4946 if (!chk) {
4947 memprintf(errmsg, "out of memory");
4948 goto error;
4949 }
4950 chk->action = TCPCHK_ACT_SEND;
4951 chk->send.type = TCPCHK_SEND_HTTP;
4952 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4953 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4954 LIST_INIT(&chk->send.http.hdrs);
4955
4956 /* Copy the method, uri and version */
4957 if (*args[cur_arg]) {
4958 if (!*args[cur_arg+1])
4959 uri = args[cur_arg];
4960 else
4961 meth = args[cur_arg];
4962 }
4963 if (*args[cur_arg+1])
4964 uri = args[cur_arg+1];
4965 if (*args[cur_arg+2])
4966 vsn = args[cur_arg+2];
4967
4968 if (meth) {
4969 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4970 chk->send.http.meth.str.area = strdup(meth);
4971 chk->send.http.meth.str.data = strlen(meth);
4972 if (!chk->send.http.meth.str.area) {
4973 memprintf(errmsg, "out of memory");
4974 goto error;
4975 }
4976 }
4977 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004978 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004979 if (!isttest(chk->send.http.uri)) {
4980 memprintf(errmsg, "out of memory");
4981 goto error;
4982 }
4983 }
4984 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004985 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004986 if (!isttest(chk->send.http.vsn)) {
4987 memprintf(errmsg, "out of memory");
4988 goto error;
4989 }
4990 }
4991
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004992 return chk;
4993
4994 error:
4995 free_tcpcheck_http_hdr(hdr);
4996 free_tcpcheck(chk, 0);
4997 return NULL;
4998}
4999
5000/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005001int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005002 const char *file, int line)
5003{
5004 struct tcpcheck_ruleset *rs = NULL;
5005 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5006 struct tcpcheck_rule *chk;
5007 char *errmsg = NULL;
5008 int err_code = 0;
5009
5010 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5011 err_code |= ERR_WARN;
5012
5013 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5014 goto out;
5015
5016 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5017 if (!chk) {
5018 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5019 goto error;
5020 }
5021 if (errmsg) {
5022 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5023 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005024 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005025 }
5026
5027 no_request:
5028 curpx->options2 &= ~PR_O2_CHK_ANY;
5029 curpx->options2 |= PR_O2_TCPCHK_CHK;
5030
5031 free_tcpcheck_vars(&rules->preset_vars);
5032 rules->list = NULL;
5033 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5034
5035 /* Deduce the ruleset name from the proxy info */
5036 chunk_printf(&trash, "*http-check-%s_%s-%d",
5037 ((curpx == defpx) ? "defaults" : curpx->id),
5038 curpx->conf.file, curpx->conf.line);
5039
5040 rs = find_tcpcheck_ruleset(b_orig(&trash));
5041 if (rs == NULL) {
5042 rs = create_tcpcheck_ruleset(b_orig(&trash));
5043 if (rs == NULL) {
5044 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5045 goto error;
5046 }
5047 }
5048
5049 rules->list = &rs->rules;
5050 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5051 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5052 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5053 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5054 rules->list = NULL;
5055 goto error;
5056 }
5057
5058 out:
5059 free(errmsg);
5060 return err_code;
5061
5062 error:
5063 free_tcpcheck_ruleset(rs);
5064 free_tcpcheck(chk, 0);
5065 err_code |= ERR_ALERT | ERR_FATAL;
5066 goto out;
5067}
5068
5069/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005070int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005071 const char *file, int line)
5072{
5073 struct tcpcheck_ruleset *rs = NULL;
5074 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5075 int err_code = 0;
5076
5077 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5078 err_code |= ERR_WARN;
5079
5080 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5081 goto out;
5082
5083 curpx->options2 &= ~PR_O2_CHK_ANY;
5084 curpx->options2 |= PR_O2_TCPCHK_CHK;
5085
5086 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5087 /* If a tcp-check rulesset is already set, do nothing */
5088 if (rules->list)
5089 goto out;
5090
5091 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5092 * get it.
5093 */
5094 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5095 goto curpx_ruleset;
5096
5097 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5098 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5099 rs = find_tcpcheck_ruleset(b_orig(&trash));
5100 if (rs)
5101 goto ruleset_found;
5102 }
5103
5104 curpx_ruleset:
5105 /* Deduce the ruleset name from the proxy info */
5106 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5107 ((curpx == defpx) ? "defaults" : curpx->id),
5108 curpx->conf.file, curpx->conf.line);
5109
5110 rs = find_tcpcheck_ruleset(b_orig(&trash));
5111 if (rs == NULL) {
5112 rs = create_tcpcheck_ruleset(b_orig(&trash));
5113 if (rs == NULL) {
5114 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5115 goto error;
5116 }
5117 }
5118
5119 ruleset_found:
5120 free_tcpcheck_vars(&rules->preset_vars);
5121 rules->list = &rs->rules;
5122 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5123 rules->flags |= TCPCHK_RULES_TCP_CHK;
5124
5125 out:
5126 return err_code;
5127
5128 error:
5129 err_code |= ERR_ALERT | ERR_FATAL;
5130 goto out;
5131}
5132
Willy Tarreau51cd5952020-06-05 12:25:38 +02005133static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005134 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005135 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5136 { 0, NULL, NULL },
5137}};
5138
5139REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5140REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5141REGISTER_POST_DEINIT(deinit_tcpchecks);
5142INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);