blob: 5ef1c69cbddf7d087e2f8355abf4daa684a6725e [file] [log] [blame]
Willy Tarreau51cd5952020-06-05 12:25:38 +02001/*
2 * Health-checks functions.
3 *
4 * Copyright 2000-2009,2020 Willy Tarreau <w@1wt.eu>
5 * Copyright 2007-2010 Krzysztof Piotr Oledzki <ole@ans.pl>
6 * Copyright 2013 Baptiste Assmann <bedis9@gmail.com>
7 * Copyright 2020 Gaetan Rivet <grive@u256.net>
8 * Copyright 2020 Christopher Faulet <cfaulet@haproxy.com>
wrightlaw9a8d8a32022-09-08 16:10:48 +01009 * Crown Copyright 2022 Defence Science and Technology Laboratory <dstlipgroup@dstl.gov.uk>
Willy Tarreau51cd5952020-06-05 12:25:38 +020010 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version
14 * 2 of the License, or (at your option) any later version.
15 *
16 */
17
18#include <sys/resource.h>
19#include <sys/socket.h>
20#include <sys/types.h>
21#include <sys/wait.h>
22#include <netinet/in.h>
23#include <netinet/tcp.h>
24#include <arpa/inet.h>
25
26#include <ctype.h>
27#include <errno.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020028#include <signal.h>
29#include <stdarg.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <time.h>
34#include <unistd.h>
35
36#include <haproxy/action.h>
37#include <haproxy/api.h>
38#include <haproxy/cfgparse.h>
39#include <haproxy/check.h>
40#include <haproxy/chunk.h>
41#include <haproxy/connection.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020042#include <haproxy/errors.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020043#include <haproxy/global.h>
44#include <haproxy/h1.h>
45#include <haproxy/http.h>
46#include <haproxy/http_htx.h>
47#include <haproxy/htx.h>
48#include <haproxy/istbuf.h>
49#include <haproxy/list.h>
50#include <haproxy/log.h>
Christopher Faulet8a0e5f82021-09-16 16:01:09 +020051#include <haproxy/net_helper.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020052#include <haproxy/protocol.h>
53#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020054#include <haproxy/regex.h>
55#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020056#include <haproxy/server.h>
57#include <haproxy/ssl_sock.h>
Willy Tarreaucb086c62022-05-27 09:47:12 +020058#include <haproxy/stconn.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020059#include <haproxy/task.h>
60#include <haproxy/tcpcheck.h>
Willy Tarreau9310f482021-10-06 16:18:40 +020061#include <haproxy/ticks.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020062#include <haproxy/tools.h>
Christopher Faulet147b8c92021-04-10 09:00:38 +020063#include <haproxy/trace.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020064#include <haproxy/vars.h>
65
66
Christopher Faulet147b8c92021-04-10 09:00:38 +020067#define TRACE_SOURCE &trace_check
68
Willy Tarreau51cd5952020-06-05 12:25:38 +020069/* Global tree to share all tcp-checks */
70struct eb_root shared_tcpchecks = EB_ROOT;
71
72
73DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
74
75/**************************************************************************/
76/*************** Init/deinit tcp-check rules and ruleset ******************/
77/**************************************************************************/
78/* Releases memory allocated for a log-format string */
79static void free_tcpcheck_fmt(struct list *fmt)
80{
81 struct logformat_node *lf, *lfb;
82
83 list_for_each_entry_safe(lf, lfb, fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +020084 LIST_DELETE(&lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +020085 release_sample_expr(lf->expr);
86 free(lf->arg);
87 free(lf);
88 }
89}
90
91/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
92void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
93{
94 if (!hdr)
95 return;
96
97 free_tcpcheck_fmt(&hdr->value);
98 istfree(&hdr->name);
99 free(hdr);
100}
101
102/* Releases memory allocated for an HTTP header list used in a tcp-check send
103 * rule
104 */
105static void free_tcpcheck_http_hdrs(struct list *hdrs)
106{
107 struct tcpcheck_http_hdr *hdr, *bhdr;
108
109 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200110 LIST_DELETE(&hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200111 free_tcpcheck_http_hdr(hdr);
112 }
113}
114
115/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
116 * tcp-check was allocated using a memory pool (it is used to instantiate email
117 * alerts).
118 */
119void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
120{
121 if (!rule)
122 return;
123
124 free(rule->comment);
125 switch (rule->action) {
126 case TCPCHK_ACT_SEND:
127 switch (rule->send.type) {
128 case TCPCHK_SEND_STRING:
129 case TCPCHK_SEND_BINARY:
130 istfree(&rule->send.data);
131 break;
132 case TCPCHK_SEND_STRING_LF:
133 case TCPCHK_SEND_BINARY_LF:
134 free_tcpcheck_fmt(&rule->send.fmt);
135 break;
136 case TCPCHK_SEND_HTTP:
137 free(rule->send.http.meth.str.area);
138 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
139 istfree(&rule->send.http.uri);
140 else
141 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
142 istfree(&rule->send.http.vsn);
143 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
144 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
145 istfree(&rule->send.http.body);
146 else
147 free_tcpcheck_fmt(&rule->send.http.body_fmt);
148 break;
149 case TCPCHK_SEND_UNDEF:
150 break;
151 }
152 break;
153 case TCPCHK_ACT_EXPECT:
154 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
155 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
156 release_sample_expr(rule->expect.status_expr);
157 switch (rule->expect.type) {
158 case TCPCHK_EXPECT_HTTP_STATUS:
159 free(rule->expect.codes.codes);
160 break;
161 case TCPCHK_EXPECT_STRING:
162 case TCPCHK_EXPECT_BINARY:
163 case TCPCHK_EXPECT_HTTP_BODY:
164 istfree(&rule->expect.data);
165 break;
166 case TCPCHK_EXPECT_STRING_REGEX:
167 case TCPCHK_EXPECT_BINARY_REGEX:
168 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
169 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
170 regex_free(rule->expect.regex);
171 break;
172 case TCPCHK_EXPECT_STRING_LF:
173 case TCPCHK_EXPECT_BINARY_LF:
174 case TCPCHK_EXPECT_HTTP_BODY_LF:
175 free_tcpcheck_fmt(&rule->expect.fmt);
176 break;
177 case TCPCHK_EXPECT_HTTP_HEADER:
178 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
179 regex_free(rule->expect.hdr.name_re);
180 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
181 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
182 else
183 istfree(&rule->expect.hdr.name);
184
185 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
186 regex_free(rule->expect.hdr.value_re);
187 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
188 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
189 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
190 istfree(&rule->expect.hdr.value);
191 break;
192 case TCPCHK_EXPECT_CUSTOM:
193 case TCPCHK_EXPECT_UNDEF:
194 break;
195 }
196 break;
197 case TCPCHK_ACT_CONNECT:
198 free(rule->connect.sni);
199 free(rule->connect.alpn);
200 release_sample_expr(rule->connect.port_expr);
201 break;
202 case TCPCHK_ACT_COMMENT:
203 break;
204 case TCPCHK_ACT_ACTION_KW:
205 free(rule->action_kw.rule);
206 break;
207 }
208
209 if (in_pool)
210 pool_free(pool_head_tcpcheck_rule, rule);
211 else
212 free(rule);
213}
214
215/* Creates a tcp-check variable used in preset variables before executing a
216 * tcp-check ruleset.
217 */
218struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
219{
220 struct tcpcheck_var *var = NULL;
221
222 var = calloc(1, sizeof(*var));
223 if (var == NULL)
224 return NULL;
225
226 var->name = istdup(name);
227 if (!isttest(var->name)) {
228 free(var);
229 return NULL;
230 }
231
232 LIST_INIT(&var->list);
233 return var;
234}
235
236/* Releases memory allocated for a preset tcp-check variable */
237void free_tcpcheck_var(struct tcpcheck_var *var)
238{
239 if (!var)
240 return;
241
242 istfree(&var->name);
243 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
244 free(var->data.u.str.area);
245 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
246 free(var->data.u.meth.str.area);
247 free(var);
248}
249
250/* Releases a list of preset tcp-check variables */
251void free_tcpcheck_vars(struct list *vars)
252{
253 struct tcpcheck_var *var, *back;
254
255 list_for_each_entry_safe(var, back, vars, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200256 LIST_DELETE(&var->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200257 free_tcpcheck_var(var);
258 }
259}
260
261/* Duplicate a list of preset tcp-check variables */
Willy Tarreau09f2e772021-02-12 08:42:30 +0100262int dup_tcpcheck_vars(struct list *dst, const struct list *src)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200263{
Willy Tarreau09f2e772021-02-12 08:42:30 +0100264 const struct tcpcheck_var *var;
265 struct tcpcheck_var *new = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200266
267 list_for_each_entry(var, src, list) {
268 new = create_tcpcheck_var(var->name);
269 if (!new)
270 goto error;
271 new->data.type = var->data.type;
272 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
273 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
274 goto error;
275 if (var->data.type == SMP_T_STR)
276 new->data.u.str.area[new->data.u.str.data] = 0;
277 }
278 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
279 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
280 goto error;
281 new->data.u.str.area[new->data.u.str.data] = 0;
282 new->data.u.meth.meth = var->data.u.meth.meth;
283 }
284 else
285 new->data.u = var->data.u;
Willy Tarreau2b718102021-04-21 07:32:39 +0200286 LIST_APPEND(dst, &new->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200287 }
288 return 1;
289
290 error:
291 free(new);
292 return 0;
293}
294
295/* Looks for a shared tcp-check ruleset given its name. */
296struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
297{
298 struct tcpcheck_ruleset *rs;
299 struct ebpt_node *node;
300
301 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
302 if (node) {
303 rs = container_of(node, typeof(*rs), node);
304 return rs;
305 }
306 return NULL;
307}
308
309/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks
310 * tree.
311 */
312struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
313{
314 struct tcpcheck_ruleset *rs;
315
316 rs = calloc(1, sizeof(*rs));
317 if (rs == NULL)
318 return NULL;
319
320 rs->node.key = strdup(name);
321 if (rs->node.key == NULL) {
322 free(rs);
323 return NULL;
324 }
325
326 LIST_INIT(&rs->rules);
327 ebis_insert(&shared_tcpchecks, &rs->node);
328 return rs;
329}
330
331/* Releases memory allocated by a tcp-check ruleset. */
332void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
333{
334 struct tcpcheck_rule *r, *rb;
335
336 if (!rs)
337 return;
338
339 ebpt_delete(&rs->node);
340 free(rs->node.key);
341 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200342 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200343 free_tcpcheck(r, 0);
344 }
345 free(rs);
346}
347
348
349/**************************************************************************/
350/**************** Everything about tcp-checks execution *******************/
351/**************************************************************************/
352/* Returns the id of a step in a tcp-check ruleset */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200353int tcpcheck_get_step_id(const struct check *check, const struct tcpcheck_rule *rule)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200354{
355 if (!rule)
356 rule = check->current_step;
357
358 /* no last started step => first step */
359 if (!rule)
360 return 1;
361
362 /* last step is the first implicit connect */
363 if (rule->index == 0 &&
364 rule->action == TCPCHK_ACT_CONNECT &&
365 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
366 return 0;
367
368 return rule->index + 1;
369}
370
371/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
372 * NULL if none was found.
373 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200374struct tcpcheck_rule *get_first_tcpcheck_rule(const struct tcpcheck_rules *rules)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200375{
376 struct tcpcheck_rule *r;
377
378 list_for_each_entry(r, rules->list, list) {
379 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
380 return r;
381 }
382 return NULL;
383}
384
385/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
386 * NULL if none was found.
387 */
388static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
389{
390 struct tcpcheck_rule *r;
391
392 list_for_each_entry_rev(r, rules->list, list) {
393 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
394 return r;
395 }
396 return NULL;
397}
398
399/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
400 * <start> or NULL if non was found. If <start> is NULL, it relies on
401 * get_first_tcpcheck_rule().
402 */
403static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
404{
405 struct tcpcheck_rule *r;
406
407 if (!start)
408 return get_first_tcpcheck_rule(rules);
409
410 r = LIST_NEXT(&start->list, typeof(r), list);
411 list_for_each_entry_from(r, rules->list, list) {
412 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
413 return r;
414 }
415 return NULL;
416}
417
418
419/* Creates info message when a tcp-check healthcheck fails on an expect rule */
420static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
421 int match, struct ist info)
422{
423 struct sample *smp;
424
425 /* Follows these step to produce the info message:
426 * 1. if info field is already provided, copy it
427 * 2. if the expect rule provides an onerror log-format string,
428 * use it to produce the message
429 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
430 * 4. Otherwise produce the generic tcp-check info message
431 */
432 if (istlen(info)) {
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100433 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200434 goto comment;
435 }
436 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
437 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
438 goto comment;
439 }
440
441 if (check->type == PR_O2_TCPCHK_CHK &&
442 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
443 goto comment;
444
445 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
446 switch (rule->expect.type) {
447 case TCPCHK_EXPECT_HTTP_STATUS:
448 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
449 break;
450 case TCPCHK_EXPECT_STRING:
451 case TCPCHK_EXPECT_HTTP_BODY:
452 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
453 tcpcheck_get_step_id(check, rule));
454 break;
455 case TCPCHK_EXPECT_BINARY:
456 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
457 break;
458 case TCPCHK_EXPECT_STRING_REGEX:
459 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
460 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
461 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
462 break;
463 case TCPCHK_EXPECT_BINARY_REGEX:
464 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
465 break;
466 case TCPCHK_EXPECT_STRING_LF:
467 case TCPCHK_EXPECT_HTTP_BODY_LF:
468 chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule));
469 break;
470 case TCPCHK_EXPECT_BINARY_LF:
471 chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule));
472 break;
473 case TCPCHK_EXPECT_CUSTOM:
474 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
475 break;
476 case TCPCHK_EXPECT_HTTP_HEADER:
477 chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
478 case TCPCHK_EXPECT_UNDEF:
479 /* Should never happen. */
480 return;
481 }
482
483 comment:
484 /* If the failing expect rule provides a comment, it is concatenated to
485 * the info message.
486 */
487 if (rule->comment) {
488 chunk_strcat(msg, " comment: ");
489 chunk_strcat(msg, rule->comment);
490 }
491
492 /* Finally, the check status code is set if the failing expect rule
493 * defines a status expression.
494 */
495 if (rule->expect.status_expr) {
496 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
497 rule->expect.status_expr, SMP_T_STR);
498
499 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
500 sample_casts[smp->data.type][SMP_T_SINT](smp))
501 check->code = smp->data.u.sint;
502 }
503
504 *(b_tail(msg)) = '\0';
505}
506
507/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
508static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
509 struct ist info)
510{
511 struct sample *smp;
512
513 /* Follows these step to produce the info message:
514 * 1. if info field is already provided, copy it
515 * 2. if the expect rule provides an onsucces log-format string,
516 * use it to produce the message
517 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
518 * 4. Otherwise produce the generic tcp-check info message
519 */
520 if (istlen(info))
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100521 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200522 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
523 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
524 &rule->expect.onsuccess_fmt);
525 else if (check->type == PR_O2_TCPCHK_CHK &&
526 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
527 chunk_strcat(msg, "(tcp-check)");
528
529 /* Finally, the check status code is set if the expect rule defines a
530 * status expression.
531 */
532 if (rule->expect.status_expr) {
533 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
534 rule->expect.status_expr, SMP_T_STR);
535
536 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
537 sample_casts[smp->data.type][SMP_T_SINT](smp))
538 check->code = smp->data.u.sint;
539 }
540
541 *(b_tail(msg)) = '\0';
542}
543
544/* Internal functions to parse and validate a MySQL packet in the context of an
545 * expect rule. It start to parse the input buffer at the offset <offset>. If
546 * <last_read> is set, no more data are expected.
547 */
548static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
549 unsigned int offset, int last_read)
550{
551 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
552 enum healthcheck_status status;
553 struct buffer *msg = NULL;
554 struct ist desc = IST_NULL;
555 unsigned int err = 0, plen = 0;
556
557
Christopher Faulet147b8c92021-04-10 09:00:38 +0200558 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
559
Willy Tarreau51cd5952020-06-05 12:25:38 +0200560 /* 3 Bytes for the packet length and 1 byte for the sequence id */
561 if (b_data(&check->bi) < offset+4) {
562 if (!last_read)
563 goto wait_more_data;
564
565 /* invalid length or truncated response */
566 status = HCHK_STATUS_L7RSP;
567 goto error;
568 }
569
570 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
571 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
572 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
573
574 if (b_data(&check->bi) < offset+plen+4) {
575 if (!last_read)
576 goto wait_more_data;
577
578 /* invalid length or truncated response */
579 status = HCHK_STATUS_L7RSP;
580 goto error;
581 }
582
583 if (*b_peek(&check->bi, offset+4) == '\xff') {
584 /* MySQL Error packet always begin with field_count = 0xff */
585 status = HCHK_STATUS_L7STS;
586 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
587 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
588 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
589 goto error;
590 }
591
592 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
593 /* Not the last rule, continue */
594 goto out;
595 }
596
597 /* We set the MySQL Version in description for information purpose
598 * FIXME : it can be cool to use MySQL Version for other purpose,
599 * like mark as down old MySQL server.
600 */
601 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
602 set_server_check_status(check, status, b_peek(&check->bi, 5));
603
604 out:
605 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200606 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200607 return ret;
608
609 error:
610 ret = TCPCHK_EVAL_STOP;
611 check->code = err;
612 msg = alloc_trash_chunk();
613 if (msg)
614 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
615 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
616 goto out;
617
618 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200619 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200620 ret = TCPCHK_EVAL_WAIT;
621 goto out;
622}
623
624/* Custom tcp-check expect function to parse and validate the MySQL initial
625 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
626 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
627 * error occurred.
628 */
629enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
630{
631 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
632}
633
634/* Custom tcp-check expect function to parse and validate the MySQL OK packet
635 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
636 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
637 * an error occurred.
638 */
639enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
640{
641 unsigned int hslen = 0;
642
643 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
644 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
645 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
646
647 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
648}
649
650/* Custom tcp-check expect function to parse and validate the LDAP bind response
651 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
652 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
653 * error occurred.
654 */
655enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
656{
657 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
658 enum healthcheck_status status;
659 struct buffer *msg = NULL;
660 struct ist desc = IST_NULL;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200661 char *ptr;
662 unsigned short nbytes = 0;
663 size_t msglen = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200664
Christopher Faulet147b8c92021-04-10 09:00:38 +0200665 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
666
Willy Tarreau51cd5952020-06-05 12:25:38 +0200667 /* Check if the server speaks LDAP (ASN.1/BER)
668 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
669 * http://tools.ietf.org/html/rfc4511
670 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200671 ptr = b_head(&check->bi) + 1;
672
Willy Tarreau51cd5952020-06-05 12:25:38 +0200673 /* size of LDAPMessage */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200674 if (*ptr & 0x80) {
675 /* For message size encoded on several bytes, we only handle
676 * size encoded on 2 or 4 bytes. There is no reason to make this
677 * part to complex because only Active Directory is known to
678 * encode BindReponse length on 4 bytes.
679 */
680 nbytes = (*ptr & 0x7f);
681 if (b_data(&check->bi) < 1 + nbytes)
682 goto too_short;
683 switch (nbytes) {
684 case 4: msglen = read_n32(ptr+1); break;
685 case 2: msglen = read_n16(ptr+1); break;
686 default:
687 status = HCHK_STATUS_L7RSP;
688 desc = ist("Not LDAPv3 protocol");
689 goto error;
690 }
691 }
692 else
693 msglen = *ptr;
694 ptr += 1 + nbytes;
695
696 if (b_data(&check->bi) < 2 + nbytes + msglen)
697 goto too_short;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200698
699 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
700 * messageID: 0x02 0x01 0x01: INTEGER 1
701 * protocolOp: 0x61: bindResponse
702 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200703 if (memcmp(ptr, "\x02\x01\x01\x61", 4) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200704 status = HCHK_STATUS_L7RSP;
705 desc = ist("Not LDAPv3 protocol");
706 goto error;
707 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200708 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200709
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200710 /* skip size of bindResponse */
711 nbytes = 0;
712 if (*ptr & 0x80)
713 nbytes = (*ptr & 0x7f);
714 ptr += 1 + nbytes;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200715
716 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
717 * ldapResult: 0x0a 0x01: ENUMERATION
718 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200719 if (memcmp(ptr, "\x0a\x01", 2) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200720 status = HCHK_STATUS_L7RSP;
721 desc = ist("Not LDAPv3 protocol");
722 goto error;
723 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200724 ptr += 2;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200725
726 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
727 * resultCode
728 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200729 check->code = *ptr;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200730 if (check->code) {
731 status = HCHK_STATUS_L7STS;
732 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
733 goto error;
734 }
735
736 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
737 set_server_check_status(check, status, "Success");
738
739 out:
740 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200741 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200742 return ret;
743
744 error:
745 ret = TCPCHK_EVAL_STOP;
746 msg = alloc_trash_chunk();
747 if (msg)
748 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
749 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
750 goto out;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200751
752 too_short:
753 if (!last_read)
754 goto wait_more_data;
755 /* invalid length or truncated response */
756 status = HCHK_STATUS_L7RSP;
757 goto error;
758
759 wait_more_data:
760 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
761 ret = TCPCHK_EVAL_WAIT;
762 goto out;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200763}
764
765/* Custom tcp-check expect function to parse and validate the SPOP hello agent
766 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
767 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
768 */
769enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
770{
771 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
772 enum healthcheck_status status;
773 struct buffer *msg = NULL;
774 struct ist desc = IST_NULL;
775 unsigned int framesz;
776
Christopher Faulet147b8c92021-04-10 09:00:38 +0200777 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200778
779 memcpy(&framesz, b_head(&check->bi), 4);
780 framesz = ntohl(framesz);
781
782 if (!last_read && b_data(&check->bi) < (4+framesz))
783 goto wait_more_data;
784
785 memset(b_orig(&trash), 0, b_size(&trash));
786 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
787 status = HCHK_STATUS_L7RSP;
788 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
789 goto error;
790 }
791
792 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
793 set_server_check_status(check, status, "SPOA server is ok");
794
795 out:
796 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200797 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200798 return ret;
799
800 error:
801 ret = TCPCHK_EVAL_STOP;
802 msg = alloc_trash_chunk();
803 if (msg)
804 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
805 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
806 goto out;
807
808 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200809 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200810 ret = TCPCHK_EVAL_WAIT;
811 goto out;
812}
813
814/* Custom tcp-check expect function to parse and validate the agent-check
815 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
816 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
817 */
818enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
819{
820 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
821 enum healthcheck_status status = HCHK_STATUS_CHECKED;
822 const char *hs = NULL; /* health status */
823 const char *as = NULL; /* admin status */
824 const char *ps = NULL; /* performance status */
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200825 const char *sc = NULL; /* maxconn */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200826 const char *err = NULL; /* first error to report */
827 const char *wrn = NULL; /* first warning to report */
828 char *cmd, *p;
829
Christopher Faulet147b8c92021-04-10 09:00:38 +0200830 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
831
Willy Tarreau51cd5952020-06-05 12:25:38 +0200832 /* We're getting an agent check response. The agent could
833 * have been disabled in the mean time with a long check
834 * still pending. It is important that we ignore the whole
835 * response.
836 */
837 if (!(check->state & CHK_ST_ENABLED))
838 goto out;
839
840 /* The agent supports strings made of a single line ended by the
841 * first CR ('\r') or LF ('\n'). This line is composed of words
842 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
843 * line may optionally contained a description of a state change
844 * after a sharp ('#'), which is only considered if a health state
845 * is announced.
846 *
847 * Words may be composed of :
848 * - a numeric weight suffixed by the percent character ('%').
849 * - a health status among "up", "down", "stopped", and "fail".
850 * - an admin status among "ready", "drain", "maint".
851 *
852 * These words may appear in any order. If multiple words of the
853 * same category appear, the last one wins.
854 */
855
856 p = b_head(&check->bi);
857 while (*p && *p != '\n' && *p != '\r')
858 p++;
859
860 if (!*p) {
861 if (!last_read)
862 goto wait_more_data;
863
864 /* at least inform the admin that the agent is mis-behaving */
865 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
866 goto out;
867 }
868
869 *p = 0;
870 cmd = b_head(&check->bi);
871
872 while (*cmd) {
873 /* look for next word */
874 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
875 cmd++;
876 continue;
877 }
878
879 if (*cmd == '#') {
880 /* this is the beginning of a health status description,
881 * skip the sharp and blanks.
882 */
883 cmd++;
884 while (*cmd == '\t' || *cmd == ' ')
885 cmd++;
886 break;
887 }
888
889 /* find the end of the word so that we have a null-terminated
890 * word between <cmd> and <p>.
891 */
892 p = cmd + 1;
893 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
894 p++;
895 if (*p)
896 *p++ = 0;
897
898 /* first, health statuses */
899 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100900 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200901 status = HCHK_STATUS_L7OKD;
902 hs = cmd;
903 }
904 else if (strcasecmp(cmd, "down") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100905 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200906 status = HCHK_STATUS_L7STS;
907 hs = cmd;
908 }
909 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100910 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200911 status = HCHK_STATUS_L7STS;
912 hs = cmd;
913 }
914 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100915 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200916 status = HCHK_STATUS_L7STS;
917 hs = cmd;
918 }
919 /* admin statuses */
920 else if (strcasecmp(cmd, "ready") == 0) {
921 as = cmd;
922 }
923 else if (strcasecmp(cmd, "drain") == 0) {
924 as = cmd;
925 }
926 else if (strcasecmp(cmd, "maint") == 0) {
927 as = cmd;
928 }
929 /* try to parse a weight here and keep the last one */
930 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
931 ps = cmd;
932 }
933 /* try to parse a maxconn here */
934 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200935 sc = cmd;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200936 }
937 else {
938 /* keep a copy of the first error */
939 if (!err)
940 err = cmd;
941 }
942 /* skip to next word */
943 cmd = p;
944 }
945 /* here, cmd points either to \0 or to the beginning of a
946 * description. Skip possible leading spaces.
947 */
948 while (*cmd == ' ' || *cmd == '\n')
949 cmd++;
950
951 /* First, update the admin status so that we avoid sending other
952 * possibly useless warnings and can also update the health if
953 * present after going back up.
954 */
955 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200956 if (strcasecmp(as, "drain") == 0) {
957 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200958 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200959 }
960 else if (strcasecmp(as, "maint") == 0) {
961 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200962 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200963 }
964 else {
965 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200966 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200967 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200968 }
969
970 /* now change weights */
971 if (ps) {
972 const char *msg;
973
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500974 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200975 msg = server_parse_weight_change_request(check->server, ps);
976 if (!wrn || !*wrn)
977 wrn = msg;
978 }
979
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200980 if (sc) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200981 const char *msg;
982
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200983 sc += strlen("maxconn:");
Willy Tarreau51cd5952020-06-05 12:25:38 +0200984
Christopher Faulet147b8c92021-04-10 09:00:38 +0200985 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury Denoyelle02742862021-06-18 11:11:36 +0200986 /* This is safe to call server_parse_maxconn_change_request
987 * because the server lock is held during the check.
988 */
Willy Tarreaubde14ad2022-05-27 10:04:04 +0200989 msg = server_parse_maxconn_change_request(check->server, sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200990 if (!wrn || !*wrn)
991 wrn = msg;
992 }
993
994 /* and finally health status */
995 if (hs) {
996 /* We'll report some of the warnings and errors we have
997 * here. Down reports are critical, we leave them untouched.
998 * Lack of report, or report of 'UP' leaves the room for
999 * ERR first, then WARN.
1000 */
1001 const char *msg = cmd;
1002 struct buffer *t;
1003
1004 if (!*msg || status == HCHK_STATUS_L7OKD) {
1005 if (err && *err)
1006 msg = err;
1007 else if (wrn && *wrn)
1008 msg = wrn;
1009 }
1010
1011 t = get_trash_chunk();
1012 chunk_printf(t, "via agent : %s%s%s%s",
1013 hs, *msg ? " (" : "",
1014 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001015 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001016 set_server_check_status(check, status, t->area);
1017 }
1018 else if (err && *err) {
1019 /* No status change but we'd like to report something odd.
1020 * Just report the current state and copy the message.
1021 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001022 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001023 chunk_printf(&trash, "agent reports an error : %s", err);
1024 set_server_check_status(check, status/*check->status*/, trash.area);
1025 }
1026 else if (wrn && *wrn) {
1027 /* No status change but we'd like to report something odd.
1028 * Just report the current state and copy the message.
1029 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001030 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001031 chunk_printf(&trash, "agent warns : %s", wrn);
1032 set_server_check_status(check, status/*check->status*/, trash.area);
1033 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001034 else {
1035 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001036 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001037 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001038
1039 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001040 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001041 return ret;
1042
1043 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001044 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001045 ret = TCPCHK_EVAL_WAIT;
1046 goto out;
1047}
1048
1049/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1050 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1051 * TCPCHK_EVAL_STOP if an error occurred.
1052 */
1053enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1054{
1055 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1056 struct tcpcheck_connect *connect = &rule->connect;
1057 struct proxy *proxy = check->proxy;
1058 struct server *s = check->server;
1059 struct task *t = check->task;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001060 struct connection *conn = sc_conn(check->sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001061 struct protocol *proto;
1062 struct xprt_ops *xprt;
1063 struct tcpcheck_rule *next;
1064 int status, port;
1065
Christopher Faulet147b8c92021-04-10 09:00:38 +02001066 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1067
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001068 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1069
1070 /* current connection already created, check if it is established or not */
1071 if (conn) {
1072 if (conn->flags & CO_FL_WAIT_XPRT) {
1073 /* We are still waiting for the connection establishment */
1074 if (next && next->action == TCPCHK_ACT_SEND) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001075 if (!(check->sc->wait_event.events & SUB_RETRY_SEND))
1076 conn->mux->subscribe(check->sc, SUB_RETRY_SEND, &check->sc->wait_event);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001077 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001078 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001079 }
1080 else
1081 ret = tcpcheck_eval_recv(check, rule);
1082 }
1083 goto out;
1084 }
1085
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001086 /* Note: here check->sc = sc = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001087
Christopher Fauletb381a502020-11-25 13:47:00 +01001088 /* Always release input and output buffer when a new connect is evaluated */
1089 check_release_buf(check, &check->bi);
1090 check_release_buf(check, &check->bo);
1091
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001092 /* No connection, prepare a new one */
Christopher Fauletdd2d0d82021-12-20 09:34:32 +01001093 conn = conn_new((s ? &s->obj_type : &proxy->obj_type));
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001094 if (!conn) {
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001095 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1096 tcpcheck_get_step_id(check, rule));
1097 if (rule->comment)
1098 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1099 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1100 ret = TCPCHK_EVAL_STOP;
Willy Tarreau4596fe22022-05-17 19:07:51 +02001101 TRACE_ERROR("stconn allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001102 goto out;
1103 }
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001104 if (sc_attach_mux(check->sc, NULL, conn) < 0) {
Christopher Faulet070b91b2022-03-31 19:27:18 +02001105 TRACE_ERROR("mux attach error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
1106 conn_free(conn);
1107 conn = NULL;
1108 status = SF_ERR_RESOURCE;
1109 goto fail_check;
1110 }
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001111 conn->ctx = check->sc;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001112 conn_set_owner(conn, check->sess, NULL);
1113
Willy Tarreau51cd5952020-06-05 12:25:38 +02001114 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001115 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001116 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001117 status = SF_ERR_RESOURCE;
1118 goto fail_check;
1119 }
1120
1121 /* connect to the connect rule addr if specified, otherwise the check
1122 * addr if specified on the server. otherwise, use the server addr (it
1123 * MUST exist at this step).
1124 */
1125 *conn->dst = (is_addr(&connect->addr)
1126 ? connect->addr
1127 : (is_addr(&check->addr) ? check->addr : s->addr));
Willy Tarreau14e7f292021-10-27 17:41:07 +02001128 proto = protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001129
1130 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001131 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001132 port = connect->port;
1133 if (!port && connect->port_expr) {
1134 struct sample *smp;
1135
1136 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1137 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1138 connect->port_expr, SMP_T_SINT);
1139 if (smp)
1140 port = smp->data.u.sint;
1141 }
1142 if (!port && is_inet_addr(&connect->addr))
1143 port = get_host_port(&connect->addr);
1144 if (!port && check->port)
1145 port = check->port;
1146 if (!port && is_inet_addr(&check->addr))
1147 port = get_host_port(&check->addr);
1148 if (!port) {
1149 /* The server MUST exist here */
1150 port = s->svc_port;
1151 }
1152 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001153 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001154
1155 xprt = ((connect->options & TCPCHK_OPT_SSL)
1156 ? xprt_get(XPRT_SSL)
1157 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1158
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001159 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001160 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001161 status = SF_ERR_RESOURCE;
1162 goto fail_check;
1163 }
1164
Christopher Fauletf7177272020-10-02 13:41:55 +02001165 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1166 conn->send_proxy_ofs = 1;
1167 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001168 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001169 }
1170 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1171 conn->send_proxy_ofs = 1;
1172 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001173 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001174 }
1175
1176 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1177 conn->send_proxy_ofs = 1;
1178 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001179 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001180 }
1181 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1182 conn->send_proxy_ofs = 1;
1183 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001184 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001185 }
1186
Willy Tarreau51cd5952020-06-05 12:25:38 +02001187 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001188 if (proto && proto->connect) {
1189 int flags = 0;
1190
Christopher Fauletf6112482022-08-30 10:31:15 +02001191 if (!next)
1192 flags |= CONNECT_DELACK_ALWAYS;
Christopher Faulet871dd822022-08-24 11:38:03 +02001193 if (connect->options & TCPCHK_OPT_HAS_DATA)
Christopher Fauletf6112482022-08-30 10:31:15 +02001194 flags |= (CONNECT_HAS_DATA|CONNECT_DELACK_ALWAYS);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001195 status = proto->connect(conn, flags);
1196 }
1197
1198 if (status != SF_ERR_NONE)
1199 goto fail_check;
1200
Christopher Faulet21ddc742020-07-01 15:26:14 +02001201 conn_set_private(conn);
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001202 conn->ctx = check->sc;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001203
Willy Tarreau51cd5952020-06-05 12:25:38 +02001204#ifdef USE_OPENSSL
1205 if (connect->sni)
1206 ssl_sock_set_servername(conn, connect->sni);
1207 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1208 ssl_sock_set_servername(conn, s->check.sni);
1209
1210 if (connect->alpn)
1211 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1212 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1213 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1214#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001215
Willy Tarreaue2226792022-04-11 18:04:33 +02001216 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER) && !(conn->flags & CO_FL_FDLESS)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001217 /* Some servers don't like reset on close */
Christopher Faulet897d6122021-12-17 17:28:35 +01001218 HA_ATOMIC_AND(&fdtab[conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001219 }
1220
1221 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1222 if (xprt_add_hs(conn) < 0)
1223 status = SF_ERR_RESOURCE;
1224 }
1225
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001226 if (conn_xprt_start(conn) < 0) {
1227 status = SF_ERR_RESOURCE;
1228 goto fail_check;
1229 }
1230
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001231 /* The mux may be initialized now if there isn't server attached to the
1232 * check (email alerts) or if there is a mux proto specified or if there
1233 * is no alpn.
1234 */
1235 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1236 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1237 const struct mux_ops *mux_ops;
1238
Christopher Faulet147b8c92021-04-10 09:00:38 +02001239 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001240 if (connect->mux_proto)
1241 mux_ops = connect->mux_proto->mux;
1242 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1243 mux_ops = check->mux_proto->mux;
1244 else {
1245 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1246 ? PROTO_MODE_HTTP
1247 : PROTO_MODE_TCP);
1248
1249 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1250 }
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001251 if (mux_ops && conn_install_mux(conn, mux_ops, check->sc, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001252 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001253 status = SF_ERR_INTERNAL;
1254 goto fail_check;
1255 }
1256 }
1257
Willy Tarreau51cd5952020-06-05 12:25:38 +02001258 fail_check:
1259 /* It can return one of :
1260 * - SF_ERR_NONE if everything's OK
1261 * - SF_ERR_SRVTO if there are no more servers
1262 * - SF_ERR_SRVCL if the connection was refused by the server
1263 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1264 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1265 * - SF_ERR_INTERNAL for any other purely internal errors
1266 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1267 * Note that we try to prevent the network stack from sending the ACK during the
1268 * connect() when a pure TCP check is used (without PROXY protocol).
1269 */
1270 switch (status) {
1271 case SF_ERR_NONE:
1272 /* we allow up to min(inter, timeout.connect) for a connection
1273 * to establish but only when timeout.check is set as it may be
1274 * to short for a full check otherwise
1275 */
1276 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1277
1278 if (proxy->timeout.check && proxy->timeout.connect) {
1279 int t_con = tick_add(now_ms, proxy->timeout.connect);
1280 t->expire = tick_first(t->expire, t_con);
1281 }
1282 break;
1283 case SF_ERR_SRVTO: /* ETIMEDOUT */
1284 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1285 case SF_ERR_PRXCOND:
1286 case SF_ERR_RESOURCE:
1287 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001288 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check, 0, 0, (size_t[]){status});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001289 chk_report_conn_err(check, errno, 0);
1290 ret = TCPCHK_EVAL_STOP;
1291 goto out;
1292 }
1293
1294 /* don't do anything until the connection is established */
1295 if (conn->flags & CO_FL_WAIT_XPRT) {
1296 if (conn->mux) {
1297 if (next && next->action == TCPCHK_ACT_SEND)
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001298 conn->mux->subscribe(check->sc, SUB_RETRY_SEND, &check->sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001299 else
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001300 conn->mux->subscribe(check->sc, SUB_RETRY_RECV, &check->sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001301 }
1302 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001303 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001304 goto out;
1305 }
1306
1307 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001308 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001309 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001310 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001311 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001312
1313 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1314 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1315
Christopher Faulet147b8c92021-04-10 09:00:38 +02001316 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001317 return ret;
1318}
1319
1320/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1321 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1322 * TCPCHK_EVAL_STOP if an error occurred.
1323 */
1324enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1325{
1326 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1327 struct tcpcheck_send *send = &rule->send;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001328 struct stconn *sc = check->sc;
1329 struct connection *conn = __sc_conn(sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001330 struct buffer *tmp = NULL;
1331 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001332 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001333
Christopher Faulet147b8c92021-04-10 09:00:38 +02001334 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1335
Christopher Fauletb381a502020-11-25 13:47:00 +01001336 if (check->state & CHK_ST_OUT_ALLOC) {
1337 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001338 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001339 goto out;
1340 }
1341
1342 if (!check_get_buf(check, &check->bo)) {
1343 check->state |= CHK_ST_OUT_ALLOC;
1344 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001345 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001346 goto out;
1347 }
1348
Christopher Faulet39066c22020-11-25 13:34:51 +01001349 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001350 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001351 TRACE_DEVEL("Data still pending, try to send it now", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Christopher Faulet39066c22020-11-25 13:34:51 +01001352 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001353 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001354
Christopher Fauletb381a502020-11-25 13:47:00 +01001355 /* Always release input buffer when a new send is evaluated */
1356 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001357
1358 switch (send->type) {
1359 case TCPCHK_SEND_STRING:
1360 case TCPCHK_SEND_BINARY:
1361 if (istlen(send->data) >= b_size(&check->bo)) {
1362 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1363 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1364 tcpcheck_get_step_id(check, rule));
1365 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1366 ret = TCPCHK_EVAL_STOP;
1367 goto out;
1368 }
1369 b_putist(&check->bo, send->data);
1370 break;
1371 case TCPCHK_SEND_STRING_LF:
1372 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1373 if (!b_data(&check->bo))
1374 goto out;
1375 break;
1376 case TCPCHK_SEND_BINARY_LF: {
1377 int len = b_size(&check->bo);
1378
1379 tmp = alloc_trash_chunk();
1380 if (!tmp)
1381 goto error_lf;
1382 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1383 if (!b_data(tmp))
1384 goto out;
1385 tmp->area[tmp->data] = '\0';
1386 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1387 goto error_lf;
1388 check->bo.data = len;
1389 break;
1390 }
1391 case TCPCHK_SEND_HTTP: {
1392 struct htx_sl *sl;
1393 struct ist meth, uri, vsn, clen, body;
1394 unsigned int slflags = 0;
1395
1396 tmp = alloc_trash_chunk();
1397 if (!tmp)
1398 goto error_htx;
1399
1400 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1401 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1402 : http_known_methods[send->http.meth.meth]);
1403 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1404 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1405 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1406 }
1407 else
1408 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1409 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1410
1411 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1412 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1413 slflags |= HTX_SL_F_VER_11;
1414 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1415 if (!isttest(send->http.body))
1416 slflags |= HTX_SL_F_BODYLESS;
1417
1418 htx = htx_from_buf(&check->bo);
1419 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1420 if (!sl)
1421 goto error_htx;
1422 sl->info.req.meth = send->http.meth.meth;
1423 if (!http_update_host(htx, sl, uri))
1424 goto error_htx;
1425
1426 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1427 struct tcpcheck_http_hdr *hdr;
1428 struct ist hdr_value;
1429
1430 list_for_each_entry(hdr, &send->http.hdrs, list) {
1431 chunk_reset(tmp);
1432 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1433 if (!b_data(tmp))
1434 continue;
1435 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1436 if (!htx_add_header(htx, hdr->name, hdr_value))
1437 goto error_htx;
1438 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1439 if (!http_update_authority(htx, sl, hdr_value))
1440 goto error_htx;
1441 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001442 if (isteqi(hdr->name, ist("connection")))
1443 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001444 }
1445
1446 }
1447 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1448 chunk_reset(tmp);
1449 httpchk_build_status_header(check->server, tmp);
1450 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1451 goto error_htx;
1452 }
1453
1454
1455 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1456 chunk_reset(tmp);
1457 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1458 body = ist2(b_orig(tmp), b_data(tmp));
1459 }
1460 else
1461 body = send->http.body;
1462 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1463
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001464 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001465 !htx_add_header(htx, ist("Content-length"), clen))
1466 goto error_htx;
1467
1468
1469 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001470 (istlen(body) && !htx_add_data_atonce(htx, body)))
1471 goto error_htx;
1472
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001473 /* no more data are expected */
1474 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001475 htx_to_buf(htx, &check->bo);
1476 break;
1477 }
1478 case TCPCHK_SEND_UNDEF:
1479 /* Should never happen. */
1480 ret = TCPCHK_EVAL_STOP;
1481 goto out;
1482 };
1483
Christopher Faulet39066c22020-11-25 13:34:51 +01001484 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001485 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001486 if (conn->mux->snd_buf(sc, &check->bo,
Willy Tarreau51cd5952020-06-05 12:25:38 +02001487 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001488 if ((conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001489 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001490 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 +02001491 goto out;
1492 }
1493 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001494 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 +02001495 conn->mux->subscribe(sc, SUB_RETRY_SEND, &sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001496 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001497 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001498 goto out;
1499 }
1500
1501 out:
1502 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001503 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1504 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001505
1506 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001507 return ret;
1508
1509 error_htx:
1510 if (htx) {
1511 htx_reset(htx);
1512 htx_to_buf(htx, &check->bo);
1513 }
1514 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1515 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001516 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 +02001517 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1518 ret = TCPCHK_EVAL_STOP;
1519 goto out;
1520
1521 error_lf:
1522 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1523 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001524 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 +02001525 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1526 ret = TCPCHK_EVAL_STOP;
1527 goto out;
1528
1529}
1530
1531/* Try to receive data before evaluating a tcp-check expect rule. Returns
1532 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1533 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1534 * TCPCHK_EVAL_STOP if an error occurred.
1535 */
1536enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1537{
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001538 struct stconn *sc = check->sc;
1539 struct connection *conn = __sc_conn(sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001540 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1541 size_t max, read, cur_read = 0;
1542 int is_empty;
1543 int read_poll = MAX_READ_POLL_LOOPS;
1544
Christopher Faulet147b8c92021-04-10 09:00:38 +02001545 TRACE_ENTER(CHK_EV_RX_DATA, check);
1546
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001547 if (sc->wait_event.events & SUB_RETRY_RECV) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001548 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001549 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001550 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001551
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001552 if (sc_ep_test(sc, SE_FL_EOS))
Willy Tarreau51cd5952020-06-05 12:25:38 +02001553 goto end_recv;
1554
Christopher Faulet147b8c92021-04-10 09:00:38 +02001555 if (check->state & CHK_ST_IN_ALLOC) {
1556 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001557 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001558 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001559
1560 if (!check_get_buf(check, &check->bi)) {
1561 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001562 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001563 goto wait_more_data;
1564 }
1565
Willy Tarreau4596fe22022-05-17 19:07:51 +02001566 /* errors on the connection and the stream connector were already checked */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001567
1568 /* prepare to detect if the mux needs more room */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001569 sc_ep_clr(sc, SE_FL_WANT_ROOM);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001570
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001571 while (sc_ep_test(sc, SE_FL_RCV_MORE) ||
1572 (!(conn->flags & CO_FL_ERROR) && !sc_ep_test(sc, SE_FL_ERROR | SE_FL_EOS))) {
1573 max = (IS_HTX_SC(sc) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1574 read = conn->mux->rcv_buf(sc, &check->bi, max, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001575 cur_read += read;
1576 if (!read ||
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001577 sc_ep_test(sc, SE_FL_WANT_ROOM) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001578 (--read_poll <= 0) ||
1579 (read < max && read >= global.tune.recv_enough))
1580 break;
1581 }
1582
1583 end_recv:
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001584 is_empty = (IS_HTX_SC(sc) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1585 if (is_empty && ((conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001586 /* Report network errors only if we got no other data. Otherwise
1587 * we'll let the upper layers decide whether the response is OK
1588 * or not. It is very common that an RST sent by the server is
1589 * reported as an error just after the last data chunk.
1590 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001591 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001592 goto stop;
1593 }
1594 if (!cur_read) {
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001595 if (sc_ep_test(sc, SE_FL_EOI)) {
Christopher Fauletd16e7dd2021-10-20 13:53:38 +02001596 /* If EOI is set, it means there is a response or an error */
1597 goto out;
1598 }
Willy Tarreau0cfcc402022-05-17 16:10:17 +02001599
Willy Tarreaubde14ad2022-05-27 10:04:04 +02001600 if (!sc_ep_test(sc, SE_FL_WANT_ROOM | SE_FL_ERROR | SE_FL_EOS)) {
1601 conn->mux->subscribe(sc, SUB_RETRY_RECV, &sc->wait_event);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001602 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001603 goto wait_more_data;
1604 }
Willy Tarreau0cfcc402022-05-17 16:10:17 +02001605
Willy Tarreau51cd5952020-06-05 12:25:38 +02001606 if (is_empty) {
1607 int status;
1608
1609 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1610 tcpcheck_get_step_id(check, rule));
1611 if (rule->comment)
1612 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1613
Christopher Faulet147b8c92021-04-10 09:00:38 +02001614 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001615 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1616 set_server_check_status(check, status, trash.area);
1617 goto stop;
1618 }
1619 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001620 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001621
1622 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001623 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1624 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001625
1626 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001627 return ret;
1628
1629 stop:
1630 ret = TCPCHK_EVAL_STOP;
1631 goto out;
1632
1633 wait_more_data:
1634 ret = TCPCHK_EVAL_WAIT;
1635 goto out;
1636}
1637
1638/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1639 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1640 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1641 * error occurred.
1642 */
1643enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1644{
1645 struct htx *htx = htxbuf(&check->bi);
1646 struct htx_sl *sl;
1647 struct htx_blk *blk;
1648 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1649 struct tcpcheck_expect *expect = &rule->expect;
1650 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1651 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1652 struct ist desc = IST_NULL;
1653 int i, match, inverse;
1654
Christopher Faulet147b8c92021-04-10 09:00:38 +02001655 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1656
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001657 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001658
1659 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001660 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001661 status = HCHK_STATUS_L7RSP;
1662 goto error;
1663 }
1664
1665 if (htx_is_empty(htx)) {
1666 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001667 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001668 status = HCHK_STATUS_L7RSP;
1669 goto error;
1670 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001671 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001672 goto wait_more_data;
1673 }
1674
1675 sl = http_get_stline(htx);
1676 check->code = sl->info.res.status;
1677
1678 if (check->server &&
1679 (check->server->proxy->options & PR_O_DISABLE404) &&
1680 (check->server->next_state != SRV_ST_STOPPED) &&
1681 (check->code == 404)) {
1682 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001683 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001684 goto out;
1685 }
1686
1687 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1688 /* Make GCC happy ; initialize match to a failure state. */
1689 match = inverse;
1690 status = expect->err_status;
1691
1692 switch (expect->type) {
1693 case TCPCHK_EXPECT_HTTP_STATUS:
1694 match = 0;
1695 for (i = 0; i < expect->codes.num; i++) {
1696 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1697 sl->info.res.status <= expect->codes.codes[i][1]) {
1698 match = 1;
1699 break;
1700 }
1701 }
1702
1703 /* Set status and description in case of error */
1704 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1705 if (LIST_ISEMPTY(&expect->onerror_fmt))
1706 desc = htx_sl_res_reason(sl);
1707 break;
1708 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1709 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1710
1711 /* Set status and description in case of error */
1712 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1713 if (LIST_ISEMPTY(&expect->onerror_fmt))
1714 desc = htx_sl_res_reason(sl);
1715 break;
1716
1717 case TCPCHK_EXPECT_HTTP_HEADER: {
1718 struct http_hdr_ctx ctx;
1719 struct ist npat, vpat, value;
1720 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1721
1722 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1723 nbuf = alloc_trash_chunk();
1724 if (!nbuf) {
1725 status = HCHK_STATUS_L7RSP;
1726 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001727 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001728 goto error;
1729 }
1730 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1731 if (!b_data(nbuf)) {
1732 status = HCHK_STATUS_L7RSP;
1733 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001734 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001735 goto error;
1736 }
1737 npat = ist2(b_orig(nbuf), b_data(nbuf));
1738 }
1739 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1740 npat = expect->hdr.name;
1741
1742 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1743 vbuf = alloc_trash_chunk();
1744 if (!vbuf) {
1745 status = HCHK_STATUS_L7RSP;
1746 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001747 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001748 goto error;
1749 }
1750 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1751 if (!b_data(vbuf)) {
1752 status = HCHK_STATUS_L7RSP;
1753 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001754 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001755 goto error;
1756 }
1757 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1758 }
1759 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1760 vpat = expect->hdr.value;
1761
1762 match = 0;
1763 ctx.blk = NULL;
1764 while (1) {
1765 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1766 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1767 if (!http_find_str_header(htx, npat, &ctx, full))
1768 goto end_of_match;
1769 break;
1770 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1771 if (!http_find_pfx_header(htx, npat, &ctx, full))
1772 goto end_of_match;
1773 break;
1774 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1775 if (!http_find_sfx_header(htx, npat, &ctx, full))
1776 goto end_of_match;
1777 break;
1778 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1779 if (!http_find_sub_header(htx, npat, &ctx, full))
1780 goto end_of_match;
1781 break;
1782 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1783 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1784 goto end_of_match;
1785 break;
1786 default:
1787 /* should never happen */
1788 goto end_of_match;
1789 }
1790
1791 /* A header has matched the name pattern, let's test its
1792 * value now (always defined from there). If there is no
1793 * value pattern, it is a good match.
1794 */
1795
1796 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1797 match = 1;
1798 goto end_of_match;
1799 }
1800
1801 value = ctx.value;
1802 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1803 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1804 if (isteq(value, vpat)) {
1805 match = 1;
1806 goto end_of_match;
1807 }
1808 break;
1809 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1810 if (istlen(value) < istlen(vpat))
1811 break;
1812 value = ist2(istptr(value), istlen(vpat));
1813 if (isteq(value, vpat)) {
1814 match = 1;
1815 goto end_of_match;
1816 }
1817 break;
1818 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1819 if (istlen(value) < istlen(vpat))
1820 break;
Tim Duesterhus4c8f75f2021-11-06 15:14:44 +01001821 value = ist2(istend(value) - istlen(vpat), istlen(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001822 if (isteq(value, vpat)) {
1823 match = 1;
1824 goto end_of_match;
1825 }
1826 break;
1827 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1828 if (isttest(istist(value, vpat))) {
1829 match = 1;
1830 goto end_of_match;
1831 }
1832 break;
1833 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1834 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1835 match = 1;
1836 goto end_of_match;
1837 }
1838 break;
1839 }
1840 }
1841
1842 end_of_match:
1843 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1844 if (LIST_ISEMPTY(&expect->onerror_fmt))
1845 desc = htx_sl_res_reason(sl);
1846 break;
1847 }
1848
1849 case TCPCHK_EXPECT_HTTP_BODY:
1850 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1851 case TCPCHK_EXPECT_HTTP_BODY_LF:
1852 match = 0;
1853 chunk_reset(&trash);
1854 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1855 enum htx_blk_type type = htx_get_blk_type(blk);
1856
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001857 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001858 break;
1859 if (type == HTX_BLK_DATA) {
1860 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1861 break;
1862 }
1863 }
1864
1865 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001866 if (!last_read) {
1867 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001868 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001869 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001870 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1871 if (LIST_ISEMPTY(&expect->onerror_fmt))
1872 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001873 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001874 goto error;
1875 }
1876
1877 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1878 tmp = alloc_trash_chunk();
1879 if (!tmp) {
1880 status = HCHK_STATUS_L7RSP;
1881 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001882 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001883 goto error;
1884 }
1885 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1886 if (!b_data(tmp)) {
1887 status = HCHK_STATUS_L7RSP;
1888 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001889 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001890 goto error;
1891 }
1892 }
1893
1894 if (!last_read &&
1895 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1896 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1897 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1898 ret = TCPCHK_EVAL_WAIT;
1899 goto out;
1900 }
1901
1902 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1903 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1904 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1905 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1906 else
1907 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1908
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001909 /* Wait for more data on mismatch only if no minimum is defined (-1),
1910 * otherwise the absence of match is already conclusive.
1911 */
1912 if (!match && !last_read && (expect->min_recv == -1)) {
1913 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001914 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001915 goto out;
1916 }
1917
Willy Tarreau51cd5952020-06-05 12:25:38 +02001918 /* Set status and description in case of error */
1919 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1920 if (LIST_ISEMPTY(&expect->onerror_fmt))
1921 desc = (inverse
1922 ? ist("HTTP check matched unwanted content")
1923 : ist("HTTP content check did not match"));
1924 break;
1925
1926
1927 default:
1928 /* should never happen */
1929 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1930 goto error;
1931 }
1932
Christopher Faulet147b8c92021-04-10 09:00:38 +02001933 if (!(match ^ inverse)) {
1934 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001935 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001936 }
1937
1938 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001939
1940 out:
1941 free_trash_chunk(tmp);
1942 free_trash_chunk(nbuf);
1943 free_trash_chunk(vbuf);
1944 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001945 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001946 return ret;
1947
1948 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001949 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001950 ret = TCPCHK_EVAL_STOP;
1951 msg = alloc_trash_chunk();
1952 if (msg)
1953 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1954 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1955 goto out;
1956
1957 wait_more_data:
1958 ret = TCPCHK_EVAL_WAIT;
1959 goto out;
1960}
1961
1962/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1963 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1964 * if an error occurred.
1965 */
1966enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1967{
1968 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1969 struct tcpcheck_expect *expect = &rule->expect;
1970 struct buffer *msg = NULL, *tmp = NULL;
1971 struct ist desc = IST_NULL;
1972 enum healthcheck_status status;
1973 int match, inverse;
1974
Christopher Faulet147b8c92021-04-10 09:00:38 +02001975 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1976
Willy Tarreau51cd5952020-06-05 12:25:38 +02001977 last_read |= b_full(&check->bi);
1978
1979 /* The current expect might need more data than the previous one, check again
1980 * that the minimum amount data required to match is respected.
1981 */
1982 if (!last_read) {
1983 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1984 (b_data(&check->bi) < istlen(expect->data))) {
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 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1990 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001991 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001992 goto out;
1993 }
1994 }
1995
1996 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1997 /* Make GCC happy ; initialize match to a failure state. */
1998 match = inverse;
1999 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
2000
2001 switch (expect->type) {
2002 case TCPCHK_EXPECT_STRING:
2003 case TCPCHK_EXPECT_BINARY:
2004 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2005 break;
2006 case TCPCHK_EXPECT_STRING_REGEX:
2007 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2008 break;
2009
2010 case TCPCHK_EXPECT_BINARY_REGEX:
2011 chunk_reset(&trash);
2012 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2013 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2014 break;
2015
2016 case TCPCHK_EXPECT_STRING_LF:
2017 case TCPCHK_EXPECT_BINARY_LF:
2018 match = 0;
2019 tmp = alloc_trash_chunk();
2020 if (!tmp) {
2021 status = HCHK_STATUS_L7RSP;
2022 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002023 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002024 goto error;
2025 }
2026 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2027 if (!b_data(tmp)) {
2028 status = HCHK_STATUS_L7RSP;
2029 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002030 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002031 goto error;
2032 }
2033 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2034 int len = tmp->data;
2035 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2036 status = HCHK_STATUS_L7RSP;
2037 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002038 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002039 goto error;
2040 }
2041 tmp->data = len;
2042 }
2043 if (b_data(&check->bi) < tmp->data) {
2044 if (!last_read) {
2045 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002046 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002047 goto out;
2048 }
2049 break;
2050 }
2051 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2052 break;
2053
2054 case TCPCHK_EXPECT_CUSTOM:
2055 if (expect->custom)
2056 ret = expect->custom(check, rule, last_read);
2057 goto out;
2058 default:
2059 /* Should never happen. */
2060 ret = TCPCHK_EVAL_STOP;
2061 goto out;
2062 }
2063
2064
2065 /* Wait for more data on mismatch only if no minimum is defined (-1),
2066 * otherwise the absence of match is already conclusive.
2067 */
2068 if (!match && !last_read && (expect->min_recv == -1)) {
2069 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002070 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002071 goto out;
2072 }
2073
2074 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002075 if (match ^ inverse) {
2076 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002077 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002078 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002079
2080 error:
2081 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002082 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002083 ret = TCPCHK_EVAL_STOP;
2084 msg = alloc_trash_chunk();
2085 if (msg)
2086 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2087 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2088 free_trash_chunk(msg);
2089
2090 out:
2091 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002092 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002093 return ret;
2094}
2095
2096/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2097 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2098 * waits.
2099 */
2100enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2101{
2102 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2103 struct act_rule *act_rule;
2104 enum act_return act_ret;
2105
2106 act_rule =rule->action_kw.rule;
2107 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2108 if (act_ret != ACT_RET_CONT) {
2109 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2110 tcpcheck_get_step_id(check, rule));
2111 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2112 ret = TCPCHK_EVAL_STOP;
2113 }
2114
2115 return ret;
2116}
2117
2118/* Executes a tcp-check ruleset. Note that this is called both from the
2119 * connection's wake() callback and from the check scheduling task. It returns
2120 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2121 * presenting the risk of an fd replacement.
2122 *
2123 * Please do NOT place any return statement in this function and only leave
2124 * via the out_end_tcpcheck label after setting retcode.
2125 */
2126int tcpcheck_main(struct check *check)
2127{
2128 struct tcpcheck_rule *rule;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002129 struct stconn *sc = check->sc;
2130 struct connection *conn = sc_conn(sc);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002131 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002132 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002133 enum tcpcheck_eval_ret eval_ret;
2134
2135 /* here, we know that the check is complete or that it failed */
2136 if (check->result != CHK_RES_UNKNOWN)
2137 goto out;
2138
Christopher Faulet147b8c92021-04-10 09:00:38 +02002139 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2140
Willy Tarreau4596fe22022-05-17 19:07:51 +02002141 /* Note: the stream connector and the connection may only be undefined before
Willy Tarreau51cd5952020-06-05 12:25:38 +02002142 * the first rule evaluation (it is always a connect rule) or when the
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002143 * stream connector allocation failed on a connect rule, during sc allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002144 */
2145
2146 /* 1- check for connection error, if any */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002147 if ((conn && conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002148 goto out_end_tcpcheck;
2149
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002150 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002151 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002152 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002153 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002154 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2155 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002156
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002157 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002158 * tcp-check variables */
2159 else {
2160 struct tcpcheck_var *var;
2161
2162 /* First evaluation, create a session */
2163 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2164 if (!check->sess) {
2165 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002166 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002167 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2168 goto out_end_tcpcheck;
2169 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002170 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002171 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2172
2173 /* Preset tcp-check variables */
2174 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2175 struct sample smp;
2176
2177 memset(&smp, 0, sizeof(smp));
2178 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2179 smp.data = var->data;
2180 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2181 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002182 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002183 }
2184
2185 /* Now evaluate the tcp-check rules */
2186
2187 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2188 check->code = 0;
2189 switch (rule->action) {
2190 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002191 /* Not the first connection, release it first */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002192 if (sc_conn(sc) && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002193 check->state |= CHK_ST_CLOSE_CONN;
2194 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002195 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002196
2197 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002198
2199 /* We are still waiting the connection gets closed */
Christopher Faulet560b8da2022-05-30 08:37:39 +02002200 if (check->state & CHK_ST_CLOSE_CONN) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002201 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002202 eval_ret = TCPCHK_EVAL_WAIT;
2203 break;
2204 }
2205
Christopher Faulet147b8c92021-04-10 09:00:38 +02002206 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002207 eval_ret = tcpcheck_eval_connect(check, rule);
2208
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002209 /* Refresh connection */
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002210 conn = sc_conn(sc);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002211 last_read = 0;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002212 must_read = (IS_HTX_SC(sc) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002213 break;
2214 case TCPCHK_ACT_SEND:
2215 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002216 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002217 eval_ret = tcpcheck_eval_send(check, rule);
2218 must_read = 1;
2219 break;
2220 case TCPCHK_ACT_EXPECT:
2221 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002222 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002223 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002224 eval_ret = tcpcheck_eval_recv(check, rule);
2225 if (eval_ret == TCPCHK_EVAL_STOP)
2226 goto out_end_tcpcheck;
2227 else if (eval_ret == TCPCHK_EVAL_WAIT)
2228 goto out;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002229 last_read = ((conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR | SE_FL_EOS));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002230 must_read = 0;
2231 }
2232
2233 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2234 ? tcpcheck_eval_expect_http(check, rule, last_read)
2235 : tcpcheck_eval_expect(check, rule, last_read));
2236
2237 if (eval_ret == TCPCHK_EVAL_WAIT) {
2238 check->current_step = rule->expect.head;
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002239 if (!(sc->wait_event.events & SUB_RETRY_RECV))
2240 conn->mux->subscribe(sc, SUB_RETRY_RECV, &sc->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002241 }
2242 break;
2243 case TCPCHK_ACT_ACTION_KW:
2244 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002245 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002246 eval_ret = tcpcheck_eval_action_kw(check, rule);
2247 break;
2248 default:
2249 /* Otherwise, just go to the next one and don't update
2250 * the current step
2251 */
2252 eval_ret = TCPCHK_EVAL_CONTINUE;
2253 break;
2254 }
2255
2256 switch (eval_ret) {
2257 case TCPCHK_EVAL_CONTINUE:
2258 break;
2259 case TCPCHK_EVAL_WAIT:
2260 goto out;
2261 case TCPCHK_EVAL_STOP:
2262 goto out_end_tcpcheck;
2263 }
2264 }
2265
2266 /* All rules was evaluated */
2267 if (check->current_step) {
2268 rule = check->current_step;
2269
Christopher Faulet147b8c92021-04-10 09:00:38 +02002270 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2271
Willy Tarreau51cd5952020-06-05 12:25:38 +02002272 if (rule->action == TCPCHK_ACT_EXPECT) {
2273 struct buffer *msg;
2274 enum healthcheck_status status;
2275
2276 if (check->server &&
2277 (check->server->proxy->options & PR_O_DISABLE404) &&
2278 (check->server->next_state != SRV_ST_STOPPED) &&
2279 (check->code == 404)) {
2280 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002281 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002282 goto out_end_tcpcheck;
2283 }
2284
2285 msg = alloc_trash_chunk();
2286 if (msg)
2287 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2288 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2289 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2290 free_trash_chunk(msg);
2291 }
2292 else if (rule->action == TCPCHK_ACT_CONNECT) {
2293 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2294 enum healthcheck_status status = HCHK_STATUS_L4OK;
2295#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002296 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002297 status = HCHK_STATUS_L6OK;
2298#endif
2299 set_server_check_status(check, status, msg);
2300 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002301 else
2302 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002303 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002304 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002305 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002306 }
2307 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002308
2309 out_end_tcpcheck:
Willy Tarreaubde14ad2022-05-27 10:04:04 +02002310 if ((conn && conn->flags & CO_FL_ERROR) || sc_ep_test(sc, SE_FL_ERROR)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002311 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002312 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002313 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002314
Christopher Fauletb381a502020-11-25 13:47:00 +01002315 /* the tcpcheck is finished, release in/out buffer now */
2316 check_release_buf(check, &check->bi);
2317 check_release_buf(check, &check->bo);
2318
Willy Tarreau51cd5952020-06-05 12:25:38 +02002319 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002320 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002321 return retcode;
2322}
2323
Willy Tarreaua631b862022-03-02 14:54:44 +01002324void tcp_check_keywords_register(struct action_kw_list *kw_list)
2325{
2326 LIST_APPEND(&tcp_check_keywords.list, &kw_list->list);
2327}
Willy Tarreau51cd5952020-06-05 12:25:38 +02002328
2329/**************************************************************************/
2330/******************* Internals to parse tcp-check rules *******************/
2331/**************************************************************************/
2332struct action_kw_list tcp_check_keywords = {
2333 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2334};
2335
2336/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2337 * returned on error.
2338 */
2339struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2340 struct list *rules, struct action_kw *kw,
2341 const char *file, int line, char **errmsg)
2342{
2343 struct tcpcheck_rule *chk = NULL;
2344 struct act_rule *actrule = NULL;
2345
Willy Tarreaud535f802021-10-11 08:49:26 +02002346 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002347 if (!actrule) {
2348 memprintf(errmsg, "out of memory");
2349 goto error;
2350 }
2351 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002352
2353 cur_arg++;
2354 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2355 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2356 goto error;
2357 }
2358
2359 chk = calloc(1, sizeof(*chk));
2360 if (!chk) {
2361 memprintf(errmsg, "out of memory");
2362 goto error;
2363 }
2364 chk->action = TCPCHK_ACT_ACTION_KW;
2365 chk->action_kw.rule = actrule;
2366 return chk;
2367
2368 error:
2369 free(actrule);
2370 return NULL;
2371}
2372
2373/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2374 * returned on error.
2375 */
2376struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2377 const char *file, int line, char **errmsg)
2378{
2379 struct tcpcheck_rule *chk = NULL;
2380 struct sockaddr_storage *sk = NULL;
2381 char *comment = NULL, *sni = NULL, *alpn = NULL;
2382 struct sample_expr *port_expr = NULL;
2383 const struct mux_proto_list *mux_proto = NULL;
2384 unsigned short conn_opts = 0;
2385 long port = 0;
2386 int alpn_len = 0;
2387
2388 list_for_each_entry(chk, rules, list) {
2389 if (chk->action == TCPCHK_ACT_CONNECT)
2390 break;
2391 if (chk->action == TCPCHK_ACT_COMMENT ||
2392 chk->action == TCPCHK_ACT_ACTION_KW ||
2393 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2394 continue;
2395
2396 memprintf(errmsg, "first step MUST also be a 'connect', "
2397 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2398 "when there is a 'connect' step in the tcp-check ruleset");
2399 goto error;
2400 }
2401
2402 cur_arg++;
2403 while (*(args[cur_arg])) {
2404 if (strcmp(args[cur_arg], "default") == 0)
2405 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2406 else if (strcmp(args[cur_arg], "addr") == 0) {
2407 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002408
2409 if (!*(args[cur_arg+1])) {
2410 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2411 goto error;
2412 }
2413
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002414 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2415 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002416 if (!sk) {
2417 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2418 goto error;
2419 }
2420
Willy Tarreau51cd5952020-06-05 12:25:38 +02002421 cur_arg++;
2422 }
2423 else if (strcmp(args[cur_arg], "port") == 0) {
2424 const char *p, *end;
2425
2426 if (!*(args[cur_arg+1])) {
2427 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2428 goto error;
2429 }
2430 cur_arg++;
2431
2432 port = 0;
2433 release_sample_expr(port_expr);
2434 p = args[cur_arg]; end = p + strlen(p);
2435 port = read_uint(&p, end);
2436 if (p != end) {
2437 int idx = 0;
2438
2439 px->conf.args.ctx = ARGC_SRV;
2440 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002441 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002442
2443 if (!port_expr) {
2444 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2445 goto error;
2446 }
2447 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2448 memprintf(errmsg, "error detected while parsing port expression : "
2449 " fetch method '%s' extracts information from '%s', "
2450 "none of which is available here.\n",
2451 args[cur_arg], sample_src_names(port_expr->fetch->use));
2452 goto error;
2453 }
2454 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2455 }
2456 else if (port > 65535 || port < 1) {
2457 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2458 args[cur_arg]);
2459 goto error;
2460 }
2461 }
2462 else if (strcmp(args[cur_arg], "proto") == 0) {
2463 if (!*(args[cur_arg+1])) {
2464 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2465 goto error;
2466 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002467 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002468 if (!mux_proto) {
2469 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2470 goto error;
2471 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002472
2473 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2474 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2475 goto error;
2476 }
2477 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2478 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2479 goto error;
2480 }
2481
Willy Tarreau51cd5952020-06-05 12:25:38 +02002482 cur_arg++;
2483 }
2484 else if (strcmp(args[cur_arg], "comment") == 0) {
2485 if (!*(args[cur_arg+1])) {
2486 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2487 goto error;
2488 }
2489 cur_arg++;
2490 free(comment);
2491 comment = strdup(args[cur_arg]);
2492 if (!comment) {
2493 memprintf(errmsg, "out of memory");
2494 goto error;
2495 }
2496 }
2497 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2498 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2499 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2500 conn_opts |= TCPCHK_OPT_SOCKS4;
2501 else if (strcmp(args[cur_arg], "linger") == 0)
2502 conn_opts |= TCPCHK_OPT_LINGER;
2503#ifdef USE_OPENSSL
2504 else if (strcmp(args[cur_arg], "ssl") == 0) {
2505 px->options |= PR_O_TCPCHK_SSL;
2506 conn_opts |= TCPCHK_OPT_SSL;
2507 }
2508 else if (strcmp(args[cur_arg], "sni") == 0) {
2509 if (!*(args[cur_arg+1])) {
2510 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2511 goto error;
2512 }
2513 cur_arg++;
2514 free(sni);
2515 sni = strdup(args[cur_arg]);
2516 if (!sni) {
2517 memprintf(errmsg, "out of memory");
2518 goto error;
2519 }
2520 }
2521 else if (strcmp(args[cur_arg], "alpn") == 0) {
2522#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2523 free(alpn);
2524 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2525 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2526 goto error;
2527 }
2528 cur_arg++;
2529#else
2530 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2531 goto error;
2532#endif
2533 }
2534#endif /* USE_OPENSSL */
2535
2536 else {
2537 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2538#ifdef USE_OPENSSL
2539 ", 'ssl', 'sni', 'alpn'"
2540#endif /* USE_OPENSSL */
2541 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2542 args[cur_arg]);
2543 goto error;
2544 }
2545 cur_arg++;
2546 }
2547
2548 chk = calloc(1, sizeof(*chk));
2549 if (!chk) {
2550 memprintf(errmsg, "out of memory");
2551 goto error;
2552 }
2553 chk->action = TCPCHK_ACT_CONNECT;
2554 chk->comment = comment;
2555 chk->connect.port = port;
2556 chk->connect.options = conn_opts;
2557 chk->connect.sni = sni;
2558 chk->connect.alpn = alpn;
2559 chk->connect.alpn_len= alpn_len;
2560 chk->connect.port_expr= port_expr;
2561 chk->connect.mux_proto= mux_proto;
2562 if (sk)
2563 chk->connect.addr = *sk;
2564 return chk;
2565
2566 error:
2567 free(alpn);
2568 free(sni);
2569 free(comment);
2570 release_sample_expr(port_expr);
2571 return NULL;
2572}
2573
2574/* Parses and creates a tcp-check send rule. NULL is returned on error */
2575struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2576 const char *file, int line, char **errmsg)
2577{
2578 struct tcpcheck_rule *chk = NULL;
2579 char *comment = NULL, *data = NULL;
2580 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2581
2582 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2583 type = TCPCHK_SEND_BINARY_LF;
2584 else if (strcmp(args[cur_arg], "send-binary") == 0)
2585 type = TCPCHK_SEND_BINARY;
2586 else if (strcmp(args[cur_arg], "send-lf") == 0)
2587 type = TCPCHK_SEND_STRING_LF;
2588 else if (strcmp(args[cur_arg], "send") == 0)
2589 type = TCPCHK_SEND_STRING;
2590
2591 if (!*(args[cur_arg+1])) {
2592 memprintf(errmsg, "'%s' expects a %s as argument",
2593 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2594 goto error;
2595 }
2596
2597 data = args[cur_arg+1];
2598
2599 cur_arg += 2;
2600 while (*(args[cur_arg])) {
2601 if (strcmp(args[cur_arg], "comment") == 0) {
2602 if (!*(args[cur_arg+1])) {
2603 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2604 goto error;
2605 }
2606 cur_arg++;
2607 free(comment);
2608 comment = strdup(args[cur_arg]);
2609 if (!comment) {
2610 memprintf(errmsg, "out of memory");
2611 goto error;
2612 }
2613 }
2614 else {
2615 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2616 args[cur_arg]);
2617 goto error;
2618 }
2619 cur_arg++;
2620 }
2621
2622 chk = calloc(1, sizeof(*chk));
2623 if (!chk) {
2624 memprintf(errmsg, "out of memory");
2625 goto error;
2626 }
2627 chk->action = TCPCHK_ACT_SEND;
2628 chk->comment = comment;
2629 chk->send.type = type;
2630
2631 switch (chk->send.type) {
2632 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002633 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002634 if (!isttest(chk->send.data)) {
2635 memprintf(errmsg, "out of memory");
2636 goto error;
2637 }
2638 break;
2639 case TCPCHK_SEND_BINARY: {
2640 int len = chk->send.data.len;
2641 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2642 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2643 goto error;
2644 }
2645 chk->send.data.len = len;
2646 break;
2647 }
2648 case TCPCHK_SEND_STRING_LF:
2649 case TCPCHK_SEND_BINARY_LF:
2650 LIST_INIT(&chk->send.fmt);
2651 px->conf.args.ctx = ARGC_SRV;
2652 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2653 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2654 goto error;
2655 }
2656 break;
2657 case TCPCHK_SEND_HTTP:
2658 case TCPCHK_SEND_UNDEF:
2659 goto error;
2660 }
2661
2662 return chk;
2663
2664 error:
2665 free(chk);
2666 free(comment);
2667 return NULL;
2668}
2669
2670/* Parses and creates a http-check send rule. NULL is returned on error */
2671struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2672 const char *file, int line, char **errmsg)
2673{
2674 struct tcpcheck_rule *chk = NULL;
2675 struct tcpcheck_http_hdr *hdr = NULL;
2676 struct http_hdr hdrs[global.tune.max_http_hdr];
2677 char *meth = NULL, *uri = NULL, *vsn = NULL;
2678 char *body = NULL, *comment = NULL;
2679 unsigned int flags = 0;
2680 int i = 0, host_hdr = -1;
2681
2682 cur_arg++;
2683 while (*(args[cur_arg])) {
2684 if (strcmp(args[cur_arg], "meth") == 0) {
2685 if (!*(args[cur_arg+1])) {
2686 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2687 goto error;
2688 }
2689 cur_arg++;
2690 meth = args[cur_arg];
2691 }
2692 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2693 if (!*(args[cur_arg+1])) {
2694 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2695 goto error;
2696 }
2697 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2698 if (strcmp(args[cur_arg], "uri-lf") == 0)
2699 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2700 cur_arg++;
2701 uri = args[cur_arg];
2702 }
2703 else if (strcmp(args[cur_arg], "ver") == 0) {
2704 if (!*(args[cur_arg+1])) {
2705 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2706 goto error;
2707 }
2708 cur_arg++;
2709 vsn = args[cur_arg];
2710 }
2711 else if (strcmp(args[cur_arg], "hdr") == 0) {
2712 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2713 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2714 goto error;
2715 }
2716
2717 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2718 if (host_hdr >= 0) {
2719 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2720 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2721 goto error;
2722 }
2723 host_hdr = i;
2724 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002725 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002726 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2727 goto skip_hdr;
2728
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002729 hdrs[i].n = ist(args[cur_arg + 1]);
2730 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002731 i++;
2732 skip_hdr:
2733 cur_arg += 2;
2734 }
2735 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2736 if (!*(args[cur_arg+1])) {
2737 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2738 goto error;
2739 }
2740 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2741 if (strcmp(args[cur_arg], "body-lf") == 0)
2742 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2743 cur_arg++;
2744 body = args[cur_arg];
2745 }
2746 else if (strcmp(args[cur_arg], "comment") == 0) {
2747 if (!*(args[cur_arg+1])) {
2748 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2749 goto error;
2750 }
2751 cur_arg++;
2752 free(comment);
2753 comment = strdup(args[cur_arg]);
2754 if (!comment) {
2755 memprintf(errmsg, "out of memory");
2756 goto error;
2757 }
2758 }
2759 else {
2760 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2761 " but got '%s' as argument.", args[cur_arg]);
2762 goto error;
2763 }
2764 cur_arg++;
2765 }
2766
2767 hdrs[i].n = hdrs[i].v = IST_NULL;
2768
2769 chk = calloc(1, sizeof(*chk));
2770 if (!chk) {
2771 memprintf(errmsg, "out of memory");
2772 goto error;
2773 }
2774 chk->action = TCPCHK_ACT_SEND;
2775 chk->comment = comment; comment = NULL;
2776 chk->send.type = TCPCHK_SEND_HTTP;
2777 chk->send.http.flags = flags;
2778 LIST_INIT(&chk->send.http.hdrs);
2779
2780 if (meth) {
2781 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2782 chk->send.http.meth.str.area = strdup(meth);
2783 chk->send.http.meth.str.data = strlen(meth);
2784 if (!chk->send.http.meth.str.area) {
2785 memprintf(errmsg, "out of memory");
2786 goto error;
2787 }
2788 }
2789 if (uri) {
2790 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2791 LIST_INIT(&chk->send.http.uri_fmt);
2792 px->conf.args.ctx = ARGC_SRV;
2793 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2794 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2795 goto error;
2796 }
2797 }
2798 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002799 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002800 if (!isttest(chk->send.http.uri)) {
2801 memprintf(errmsg, "out of memory");
2802 goto error;
2803 }
2804 }
2805 }
2806 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002807 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002808 if (!isttest(chk->send.http.vsn)) {
2809 memprintf(errmsg, "out of memory");
2810 goto error;
2811 }
2812 }
2813 for (i = 0; istlen(hdrs[i].n); i++) {
2814 hdr = calloc(1, sizeof(*hdr));
2815 if (!hdr) {
2816 memprintf(errmsg, "out of memory");
2817 goto error;
2818 }
2819 LIST_INIT(&hdr->value);
2820 hdr->name = istdup(hdrs[i].n);
2821 if (!isttest(hdr->name)) {
2822 memprintf(errmsg, "out of memory");
2823 goto error;
2824 }
2825
2826 ist0(hdrs[i].v);
2827 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2828 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002829 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002830 hdr = NULL;
2831 }
2832
2833 if (body) {
2834 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2835 LIST_INIT(&chk->send.http.body_fmt);
2836 px->conf.args.ctx = ARGC_SRV;
2837 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2838 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2839 goto error;
2840 }
2841 }
2842 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002843 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002844 if (!isttest(chk->send.http.body)) {
2845 memprintf(errmsg, "out of memory");
2846 goto error;
2847 }
2848 }
2849 }
2850
2851 return chk;
2852
2853 error:
2854 free_tcpcheck_http_hdr(hdr);
2855 free_tcpcheck(chk, 0);
2856 free(comment);
2857 return NULL;
2858}
2859
2860/* Parses and creates a http-check comment rule. NULL is returned on error */
2861struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2862 const char *file, int line, char **errmsg)
2863{
2864 struct tcpcheck_rule *chk = NULL;
2865 char *comment = NULL;
2866
2867 if (!*(args[cur_arg+1])) {
2868 memprintf(errmsg, "expects a string as argument");
2869 goto error;
2870 }
2871 cur_arg++;
2872 comment = strdup(args[cur_arg]);
2873 if (!comment) {
2874 memprintf(errmsg, "out of memory");
2875 goto error;
2876 }
2877
2878 chk = calloc(1, sizeof(*chk));
2879 if (!chk) {
2880 memprintf(errmsg, "out of memory");
2881 goto error;
2882 }
2883 chk->action = TCPCHK_ACT_COMMENT;
2884 chk->comment = comment;
2885 return chk;
2886
2887 error:
2888 free(comment);
2889 return NULL;
2890}
2891
2892/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2893 * on error. <proto> is set to the right protocol flags (covered by the
2894 * TCPCHK_RULES_PROTO_CHK mask).
2895 */
2896struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2897 struct list *rules, unsigned int proto,
2898 const char *file, int line, char **errmsg)
2899{
2900 struct tcpcheck_rule *prev_check, *chk = NULL;
2901 struct sample_expr *status_expr = NULL;
2902 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2903 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2904 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2905 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2906 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2907 unsigned int flags = 0;
2908 long min_recv = -1;
2909 int inverse = 0;
2910
2911 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2912 if (!*(args[cur_arg+1])) {
2913 memprintf(errmsg, "expects at least a matching pattern as arguments");
2914 goto error;
2915 }
2916
2917 cur_arg++;
2918 while (*(args[cur_arg])) {
2919 int in_pattern = 0;
2920
2921 rescan:
2922 if (strcmp(args[cur_arg], "min-recv") == 0) {
2923 if (in_pattern) {
2924 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2925 goto error;
2926 }
2927 if (!*(args[cur_arg+1])) {
2928 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2929 goto error;
2930 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002931 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002932 cur_arg++;
2933 min_recv = atol(args[cur_arg]);
2934 if (min_recv < -1 || min_recv > INT_MAX) {
2935 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2936 goto error;
2937 }
2938 }
2939 else if (*(args[cur_arg]) == '!') {
2940 in_pattern = 1;
2941 while (*(args[cur_arg]) == '!') {
2942 inverse = !inverse;
2943 args[cur_arg]++;
2944 }
2945 if (!*(args[cur_arg]))
2946 cur_arg++;
2947 goto rescan;
2948 }
2949 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2950 if (type != TCPCHK_EXPECT_UNDEF) {
2951 memprintf(errmsg, "only on pattern expected");
2952 goto error;
2953 }
2954 if (proto != TCPCHK_RULES_HTTP_CHK)
2955 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2956 else
2957 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2958
2959 if (!*(args[cur_arg+1])) {
2960 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2961 goto error;
2962 }
2963 cur_arg++;
2964 pattern = args[cur_arg];
2965 }
2966 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2967 if (proto == TCPCHK_RULES_HTTP_CHK)
2968 goto bad_http_kw;
2969 if (type != TCPCHK_EXPECT_UNDEF) {
2970 memprintf(errmsg, "only on pattern expected");
2971 goto error;
2972 }
2973 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2974
2975 if (!*(args[cur_arg+1])) {
2976 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2977 goto error;
2978 }
2979 cur_arg++;
2980 pattern = args[cur_arg];
2981 }
2982 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2983 if (type != TCPCHK_EXPECT_UNDEF) {
2984 memprintf(errmsg, "only on pattern expected");
2985 goto error;
2986 }
2987 if (proto != TCPCHK_RULES_HTTP_CHK)
2988 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2989 else {
2990 if (*(args[cur_arg]) != 's')
2991 goto bad_http_kw;
2992 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2993 }
2994
2995 if (!*(args[cur_arg+1])) {
2996 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2997 goto error;
2998 }
2999 cur_arg++;
3000 pattern = args[cur_arg];
3001 }
3002 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3003 if (proto != TCPCHK_RULES_HTTP_CHK)
3004 goto bad_tcp_kw;
3005 if (type != TCPCHK_EXPECT_UNDEF) {
3006 memprintf(errmsg, "only on pattern expected");
3007 goto error;
3008 }
3009 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3010
3011 if (!*(args[cur_arg+1])) {
3012 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3013 goto error;
3014 }
3015 cur_arg++;
3016 pattern = args[cur_arg];
3017 }
3018 else if (strcmp(args[cur_arg], "custom") == 0) {
3019 if (in_pattern) {
3020 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3021 goto error;
3022 }
3023 if (type != TCPCHK_EXPECT_UNDEF) {
3024 memprintf(errmsg, "only on pattern expected");
3025 goto error;
3026 }
3027 type = TCPCHK_EXPECT_CUSTOM;
3028 }
3029 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3030 int orig_arg = cur_arg;
3031
3032 if (proto != TCPCHK_RULES_HTTP_CHK)
3033 goto bad_tcp_kw;
3034 if (type != TCPCHK_EXPECT_UNDEF) {
3035 memprintf(errmsg, "only on pattern expected");
3036 goto error;
3037 }
3038 type = TCPCHK_EXPECT_HTTP_HEADER;
3039
3040 if (strcmp(args[cur_arg], "fhdr") == 0)
3041 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3042
3043 /* Parse the name pattern, mandatory */
3044 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3045 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3046 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3047 args[orig_arg]);
3048 goto error;
3049 }
3050
3051 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3052 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3053
3054 cur_arg += 2;
3055 if (strcmp(args[cur_arg], "-m") == 0) {
3056 if (!*(args[cur_arg+1])) {
3057 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3058 args[orig_arg], args[cur_arg]);
3059 goto error;
3060 }
3061 if (strcmp(args[cur_arg+1], "str") == 0)
3062 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3063 else if (strcmp(args[cur_arg+1], "beg") == 0)
3064 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3065 else if (strcmp(args[cur_arg+1], "end") == 0)
3066 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3067 else if (strcmp(args[cur_arg+1], "sub") == 0)
3068 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3069 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3070 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3071 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3072 args[orig_arg]);
3073 goto error;
3074 }
3075 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3076 }
3077 else {
3078 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3079 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3080 goto error;
3081 }
3082 cur_arg += 2;
3083 }
3084 else
3085 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3086 npat = args[cur_arg];
3087
3088 if (!*(args[cur_arg+1]) ||
3089 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3090 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3091 goto next;
3092 }
3093 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3094 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3095
3096 /* Parse the value pattern, optional */
3097 if (strcmp(args[cur_arg+2], "-m") == 0) {
3098 cur_arg += 2;
3099 if (!*(args[cur_arg+1])) {
3100 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3101 args[orig_arg], args[cur_arg]);
3102 goto error;
3103 }
3104 if (strcmp(args[cur_arg+1], "str") == 0)
3105 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3106 else if (strcmp(args[cur_arg+1], "beg") == 0)
3107 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3108 else if (strcmp(args[cur_arg+1], "end") == 0)
3109 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3110 else if (strcmp(args[cur_arg+1], "sub") == 0)
3111 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3112 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3113 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3114 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3115 args[orig_arg]);
3116 goto error;
3117 }
3118 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3119 }
3120 else {
3121 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3122 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3123 goto error;
3124 }
3125 }
3126 else
3127 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3128
3129 if (!*(args[cur_arg+2])) {
3130 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3131 goto error;
3132 }
3133 vpat = args[cur_arg+2];
3134 cur_arg += 2;
3135 }
3136 else if (strcmp(args[cur_arg], "comment") == 0) {
3137 if (in_pattern) {
3138 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3139 goto error;
3140 }
3141 if (!*(args[cur_arg+1])) {
3142 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3143 goto error;
3144 }
3145 cur_arg++;
3146 free(comment);
3147 comment = strdup(args[cur_arg]);
3148 if (!comment) {
3149 memprintf(errmsg, "out of memory");
3150 goto error;
3151 }
3152 }
3153 else if (strcmp(args[cur_arg], "on-success") == 0) {
3154 if (in_pattern) {
3155 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3156 goto error;
3157 }
3158 if (!*(args[cur_arg+1])) {
3159 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3160 goto error;
3161 }
3162 cur_arg++;
3163 on_success_msg = args[cur_arg];
3164 }
3165 else if (strcmp(args[cur_arg], "on-error") == 0) {
3166 if (in_pattern) {
3167 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3168 goto error;
3169 }
3170 if (!*(args[cur_arg+1])) {
3171 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3172 goto error;
3173 }
3174 cur_arg++;
3175 on_error_msg = args[cur_arg];
3176 }
3177 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3178 if (in_pattern) {
3179 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3180 goto error;
3181 }
3182 if (!*(args[cur_arg+1])) {
3183 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3184 goto error;
3185 }
3186 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3187 ok_st = HCHK_STATUS_L7OKD;
3188 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3189 ok_st = HCHK_STATUS_L7OKCD;
3190 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3191 ok_st = HCHK_STATUS_L6OK;
3192 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3193 ok_st = HCHK_STATUS_L4OK;
3194 else {
3195 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3196 args[cur_arg], args[cur_arg+1]);
3197 goto error;
3198 }
3199 cur_arg++;
3200 }
3201 else if (strcmp(args[cur_arg], "error-status") == 0) {
3202 if (in_pattern) {
3203 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3204 goto error;
3205 }
3206 if (!*(args[cur_arg+1])) {
3207 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3208 goto error;
3209 }
3210 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3211 err_st = HCHK_STATUS_L7RSP;
3212 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3213 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003214 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3215 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003216 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3217 err_st = HCHK_STATUS_L6RSP;
3218 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3219 err_st = HCHK_STATUS_L4CON;
3220 else {
3221 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3222 args[cur_arg], args[cur_arg+1]);
3223 goto error;
3224 }
3225 cur_arg++;
3226 }
3227 else if (strcmp(args[cur_arg], "status-code") == 0) {
3228 int idx = 0;
3229
3230 if (in_pattern) {
3231 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3232 goto error;
3233 }
3234 if (!*(args[cur_arg+1])) {
3235 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3236 goto error;
3237 }
3238
3239 cur_arg++;
3240 release_sample_expr(status_expr);
3241 px->conf.args.ctx = ARGC_SRV;
3242 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003243 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003244 if (!status_expr) {
3245 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3246 goto error;
3247 }
3248 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3249 memprintf(errmsg, "error detected while parsing status-code expression : "
3250 " fetch method '%s' extracts information from '%s', "
3251 "none of which is available here.\n",
3252 args[cur_arg], sample_src_names(status_expr->fetch->use));
3253 goto error;
3254 }
3255 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3256 }
3257 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3258 if (in_pattern) {
3259 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3260 goto error;
3261 }
3262 if (!*(args[cur_arg+1])) {
3263 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3264 goto error;
3265 }
3266 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3267 tout_st = HCHK_STATUS_L7TOUT;
3268 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3269 tout_st = HCHK_STATUS_L6TOUT;
3270 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3271 tout_st = HCHK_STATUS_L4TOUT;
3272 else {
3273 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3274 args[cur_arg], args[cur_arg+1]);
3275 goto error;
3276 }
3277 cur_arg++;
3278 }
3279 else {
3280 if (proto == TCPCHK_RULES_HTTP_CHK) {
3281 bad_http_kw:
3282 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3283 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3284 }
3285 else {
3286 bad_tcp_kw:
3287 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3288 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3289 }
3290 goto error;
3291 }
3292 next:
3293 cur_arg++;
3294 }
3295
3296 chk = calloc(1, sizeof(*chk));
3297 if (!chk) {
3298 memprintf(errmsg, "out of memory");
3299 goto error;
3300 }
3301 chk->action = TCPCHK_ACT_EXPECT;
3302 LIST_INIT(&chk->expect.onerror_fmt);
3303 LIST_INIT(&chk->expect.onsuccess_fmt);
3304 chk->comment = comment; comment = NULL;
3305 chk->expect.type = type;
3306 chk->expect.min_recv = min_recv;
3307 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3308 chk->expect.ok_status = ok_st;
3309 chk->expect.err_status = err_st;
3310 chk->expect.tout_status = tout_st;
3311 chk->expect.status_expr = status_expr; status_expr = NULL;
3312
3313 if (on_success_msg) {
3314 px->conf.args.ctx = ARGC_SRV;
3315 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3316 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3317 goto error;
3318 }
3319 }
3320 if (on_error_msg) {
3321 px->conf.args.ctx = ARGC_SRV;
3322 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3323 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3324 goto error;
3325 }
3326 }
3327
3328 switch (chk->expect.type) {
3329 case TCPCHK_EXPECT_HTTP_STATUS: {
3330 const char *p = pattern;
3331 unsigned int c1,c2;
3332
3333 chk->expect.codes.codes = NULL;
3334 chk->expect.codes.num = 0;
3335 while (1) {
3336 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3337 if (*p == '-') {
3338 p++;
3339 c2 = read_uint(&p, pattern + strlen(pattern));
3340 }
3341 if (c1 > c2) {
3342 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3343 goto error;
3344 }
3345
3346 chk->expect.codes.num++;
3347 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3348 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3349 if (!chk->expect.codes.codes) {
3350 memprintf(errmsg, "out of memory");
3351 goto error;
3352 }
3353 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3354 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3355
3356 if (*p == '\0')
3357 break;
3358 if (*p != ',') {
3359 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3360 goto error;
3361 }
3362 p++;
3363 }
3364 break;
3365 }
3366 case TCPCHK_EXPECT_STRING:
3367 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003368 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003369 if (!isttest(chk->expect.data)) {
3370 memprintf(errmsg, "out of memory");
3371 goto error;
3372 }
3373 break;
3374 case TCPCHK_EXPECT_BINARY: {
3375 int len = chk->expect.data.len;
3376
3377 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3378 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3379 goto error;
3380 }
3381 chk->expect.data.len = len;
3382 break;
3383 }
3384 case TCPCHK_EXPECT_STRING_REGEX:
3385 case TCPCHK_EXPECT_BINARY_REGEX:
3386 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3387 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3388 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3389 if (!chk->expect.regex)
3390 goto error;
3391 break;
3392
3393 case TCPCHK_EXPECT_STRING_LF:
3394 case TCPCHK_EXPECT_BINARY_LF:
3395 case TCPCHK_EXPECT_HTTP_BODY_LF:
3396 LIST_INIT(&chk->expect.fmt);
3397 px->conf.args.ctx = ARGC_SRV;
3398 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3399 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3400 goto error;
3401 }
3402 break;
3403
3404 case TCPCHK_EXPECT_HTTP_HEADER:
3405 if (!npat) {
3406 memprintf(errmsg, "unexpected error, undefined header name pattern");
3407 goto error;
3408 }
3409 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3410 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3411 if (!chk->expect.hdr.name_re)
3412 goto error;
3413 }
3414 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3415 px->conf.args.ctx = ARGC_SRV;
3416 LIST_INIT(&chk->expect.hdr.name_fmt);
3417 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3418 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3419 goto error;
3420 }
3421 }
3422 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003423 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003424 if (!isttest(chk->expect.hdr.name)) {
3425 memprintf(errmsg, "out of memory");
3426 goto error;
3427 }
3428 }
3429
3430 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3431 chk->expect.hdr.value = IST_NULL;
3432 break;
3433 }
3434
3435 if (!vpat) {
3436 memprintf(errmsg, "unexpected error, undefined header value pattern");
3437 goto error;
3438 }
3439 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3440 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3441 if (!chk->expect.hdr.value_re)
3442 goto error;
3443 }
3444 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3445 px->conf.args.ctx = ARGC_SRV;
3446 LIST_INIT(&chk->expect.hdr.value_fmt);
3447 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3448 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3449 goto error;
3450 }
3451 }
3452 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003453 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003454 if (!isttest(chk->expect.hdr.value)) {
3455 memprintf(errmsg, "out of memory");
3456 goto error;
3457 }
3458 }
3459
3460 break;
3461 case TCPCHK_EXPECT_CUSTOM:
3462 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3463 break;
3464 case TCPCHK_EXPECT_UNDEF:
3465 memprintf(errmsg, "pattern not found");
3466 goto error;
3467 }
3468
3469 /* All tcp-check expect points back to the first inverse expect rule in
3470 * a chain of one or more expect rule, potentially itself.
3471 */
3472 chk->expect.head = chk;
3473 list_for_each_entry_rev(prev_check, rules, list) {
3474 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3475 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3476 chk->expect.head = prev_check;
3477 continue;
3478 }
3479 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3480 break;
3481 }
3482 return chk;
3483
3484 error:
3485 free_tcpcheck(chk, 0);
3486 free(comment);
3487 release_sample_expr(status_expr);
3488 return NULL;
3489}
3490
3491/* Overwrites fields of the old http send rule with those of the new one. When
3492 * replaced, old values are freed and replaced by the new ones. New values are
3493 * not copied but transferred. At the end <new> should be empty and can be
3494 * safely released. This function never fails.
3495 */
3496void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3497{
3498 struct logformat_node *lf, *lfb;
3499 struct tcpcheck_http_hdr *hdr, *bhdr;
3500
3501
3502 if (new->send.http.meth.str.area) {
3503 free(old->send.http.meth.str.area);
3504 old->send.http.meth.meth = new->send.http.meth.meth;
3505 old->send.http.meth.str.area = new->send.http.meth.str.area;
3506 old->send.http.meth.str.data = new->send.http.meth.str.data;
3507 new->send.http.meth.str = BUF_NULL;
3508 }
3509
3510 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3511 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3512 istfree(&old->send.http.uri);
3513 else
3514 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3515 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3516 old->send.http.uri = new->send.http.uri;
3517 new->send.http.uri = IST_NULL;
3518 }
3519 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3520 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3521 istfree(&old->send.http.uri);
3522 else
3523 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3524 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3525 LIST_INIT(&old->send.http.uri_fmt);
3526 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003527 LIST_DELETE(&lf->list);
3528 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003529 }
3530 }
3531
3532 if (isttest(new->send.http.vsn)) {
3533 istfree(&old->send.http.vsn);
3534 old->send.http.vsn = new->send.http.vsn;
3535 new->send.http.vsn = IST_NULL;
3536 }
3537
Christopher Faulet4c8e58d2022-07-05 15:33:53 +02003538 if (!LIST_ISEMPTY(&new->send.http.hdrs)) {
3539 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3540 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
3541 LIST_DELETE(&hdr->list);
3542 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
3543 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02003544 }
3545
3546 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3547 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3548 istfree(&old->send.http.body);
3549 else
3550 free_tcpcheck_fmt(&old->send.http.body_fmt);
3551 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3552 old->send.http.body = new->send.http.body;
3553 new->send.http.body = IST_NULL;
3554 }
3555 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3556 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3557 istfree(&old->send.http.body);
3558 else
3559 free_tcpcheck_fmt(&old->send.http.body_fmt);
3560 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3561 LIST_INIT(&old->send.http.body_fmt);
3562 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003563 LIST_DELETE(&lf->list);
3564 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003565 }
3566 }
3567}
3568
3569/* Internal function used to add an http-check rule in a list during the config
3570 * parsing step. Depending on its type, and the previously inserted rules, a
3571 * specific action may be performed or an error may be reported. This functions
3572 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3573 * message.
3574 */
3575int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3576{
3577 struct tcpcheck_rule *r;
3578
3579 /* the implicit send rule coming from an "option httpchk" line must be
3580 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003581 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003582 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003583 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003584 * sure the ruleset remains valid.
3585 */
3586
3587 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3588 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3589 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3590 * following tests are performed :
3591 *
3592 * 1- If there is no such rule or if it is not a send rule, the implicit send
3593 * rule is pushed in front of the ruleset
3594 *
3595 * 2- If it is another implicit send rule, it is replaced with the new one.
3596 *
3597 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3598 * both, overwriting the old send rule (the explicit one) with info of the
3599 * new send rule (the implicit one).
3600 */
3601 r = get_first_tcpcheck_rule(rules);
3602 if (r && r->action == TCPCHK_ACT_CONNECT)
3603 r = get_next_tcpcheck_rule(rules, r);
3604 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003605 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003606 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003607 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003608 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003609 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003610 }
3611 else {
3612 tcpcheck_overwrite_send_http_rule(r, chk);
3613 free_tcpcheck(chk, 0);
3614 }
3615 }
3616 else {
3617 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3618 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3619 * with an existing implicit send rule, if any. At the end, if there is no error,
3620 * the rule is appended to the list.
3621 */
3622
3623 r = get_last_tcpcheck_rule(rules);
3624 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3625 /* no error */;
3626 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3627 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3628 chk->index+1);
3629 return 0;
3630 }
3631 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3632 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3633 chk->index+1);
3634 return 0;
3635 }
3636 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3637 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3638 chk->index+1);
3639 return 0;
3640 }
3641
3642 if (chk->action == TCPCHK_ACT_SEND) {
3643 r = get_first_tcpcheck_rule(rules);
3644 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3645 tcpcheck_overwrite_send_http_rule(r, chk);
3646 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003647 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003648 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3649 chk = r;
3650 }
3651 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003652 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003653 }
3654 return 1;
3655}
3656
3657/* Check tcp-check health-check configuration for the proxy <px>. */
3658static int check_proxy_tcpcheck(struct proxy *px)
3659{
3660 struct tcpcheck_rule *chk, *back;
3661 char *comment = NULL, *errmsg = NULL;
3662 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003663 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003664
3665 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3666 deinit_proxy_tcpcheck(px);
3667 goto out;
3668 }
3669
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003670 ha_free(&px->check_command);
3671 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003672
3673 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003674 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003675 ret |= ERR_ALERT | ERR_FATAL;
3676 goto out;
3677 }
3678
3679 /* HTTP ruleset only : */
3680 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3681 struct tcpcheck_rule *next;
3682
3683 /* move remaining implicit send rule from "option httpchk" line to the right place.
3684 * If such rule exists, it must be the first one. In this case, the rule is moved
3685 * after the first connect rule, if any. Otherwise, nothing is done.
3686 */
3687 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3688 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3689 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3690 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003691 LIST_DELETE(&chk->list);
3692 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003693 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003694 }
3695 }
3696
3697 /* add implicit expect rule if the last one is a send. It is inherited from previous
3698 * versions where the http expect rule was optional. Now it is possible to chained
3699 * send/expect rules but the last expect may still be implicit.
3700 */
3701 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3702 if (chk && chk->action == TCPCHK_ACT_SEND) {
3703 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3704 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3705 px->conf.file, px->conf.line, &errmsg);
3706 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003707 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003708 "(%s).\n", px->id, errmsg);
3709 free(errmsg);
3710 ret |= ERR_ALERT | ERR_FATAL;
3711 goto out;
3712 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003713 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003714 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003715 }
3716 }
3717
3718 /* For all ruleset: */
3719
3720 /* If there is no connect rule preceding all send / expect rules, an
3721 * implicit one is inserted before all others.
3722 */
3723 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3724 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3725 chk = calloc(1, sizeof(*chk));
3726 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003727 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003728 "(out of memory).\n", px->id);
3729 ret |= ERR_ALERT | ERR_FATAL;
3730 goto out;
3731 }
3732 chk->action = TCPCHK_ACT_CONNECT;
3733 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003734 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003735 }
3736
3737 /* Remove all comment rules. To do so, when a such rule is found, the
3738 * comment is assigned to the following rule(s).
3739 */
3740 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Christopher Faulet871dd822022-08-24 11:38:03 +02003741 struct tcpcheck_rule *next;
3742
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003743 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3744 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003745
3746 prev_action = chk->action;
3747 switch (chk->action) {
3748 case TCPCHK_ACT_COMMENT:
3749 free(comment);
3750 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003751 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003752 free(chk);
3753 break;
3754 case TCPCHK_ACT_CONNECT:
3755 if (!chk->comment && comment)
3756 chk->comment = strdup(comment);
Christopher Faulet871dd822022-08-24 11:38:03 +02003757 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3758 if (next && next->action == TCPCHK_ACT_SEND)
3759 chk->connect.options |= TCPCHK_OPT_HAS_DATA;
Tim Duesterhus588b3142020-05-29 14:35:51 +02003760 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003761 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003762 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003763 break;
3764 case TCPCHK_ACT_SEND:
3765 case TCPCHK_ACT_EXPECT:
3766 if (!chk->comment && comment)
3767 chk->comment = strdup(comment);
3768 break;
3769 }
3770 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003771 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003772
3773 out:
3774 return ret;
3775}
3776
3777void deinit_proxy_tcpcheck(struct proxy *px)
3778{
3779 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3780 px->tcpcheck_rules.flags = 0;
3781 px->tcpcheck_rules.list = NULL;
3782}
3783
3784static void deinit_tcpchecks()
3785{
3786 struct tcpcheck_ruleset *rs;
3787 struct tcpcheck_rule *r, *rb;
3788 struct ebpt_node *node, *next;
3789
3790 node = ebpt_first(&shared_tcpchecks);
3791 while (node) {
3792 next = ebpt_next(node);
3793 ebpt_delete(node);
3794 free(node->key);
3795 rs = container_of(node, typeof(*rs), node);
3796 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003797 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003798 free_tcpcheck(r, 0);
3799 }
3800 free(rs);
3801 node = next;
3802 }
3803}
3804
3805int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3806{
3807 struct tcpcheck_rule *tcpcheck, *prev_check;
3808 struct tcpcheck_expect *expect;
3809
Willy Tarreau6922e552021-03-22 21:11:45 +01003810 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003811 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003812 tcpcheck->action = TCPCHK_ACT_EXPECT;
3813
3814 expect = &tcpcheck->expect;
3815 expect->type = TCPCHK_EXPECT_STRING;
3816 LIST_INIT(&expect->onerror_fmt);
3817 LIST_INIT(&expect->onsuccess_fmt);
3818 expect->ok_status = HCHK_STATUS_L7OKD;
3819 expect->err_status = HCHK_STATUS_L7RSP;
3820 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003821 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003822 if (!isttest(expect->data)) {
3823 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3824 return 0;
3825 }
3826
3827 /* All tcp-check expect points back to the first inverse expect rule
3828 * in a chain of one or more expect rule, potentially itself.
3829 */
3830 tcpcheck->expect.head = tcpcheck;
3831 list_for_each_entry_rev(prev_check, rules->list, list) {
3832 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3833 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3834 tcpcheck->expect.head = prev_check;
3835 continue;
3836 }
3837 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3838 break;
3839 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003840 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003841 return 1;
3842}
3843
3844int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3845{
3846 struct tcpcheck_rule *tcpcheck;
3847 struct tcpcheck_send *send;
3848 const char *in;
3849 char *dst;
3850 int i;
3851
Willy Tarreau6922e552021-03-22 21:11:45 +01003852 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003853 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003854 tcpcheck->action = TCPCHK_ACT_SEND;
3855
3856 send = &tcpcheck->send;
3857 send->type = TCPCHK_SEND_STRING;
3858
3859 for (i = 0; strs[i]; i++)
3860 send->data.len += strlen(strs[i]);
3861
3862 send->data.ptr = malloc(istlen(send->data) + 1);
3863 if (!isttest(send->data)) {
3864 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3865 return 0;
3866 }
3867
3868 dst = istptr(send->data);
3869 for (i = 0; strs[i]; i++)
3870 for (in = strs[i]; (*dst = *in++); dst++);
3871 *dst = 0;
3872
Willy Tarreau2b718102021-04-21 07:32:39 +02003873 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003874 return 1;
3875}
3876
3877/* Parses the "tcp-check" proxy keyword */
3878static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003879 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003880 char **errmsg)
3881{
3882 struct tcpcheck_ruleset *rs = NULL;
3883 struct tcpcheck_rule *chk = NULL;
3884 int index, cur_arg, ret = 0;
3885
3886 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3887 ret = 1;
3888
3889 /* Deduce the ruleset name from the proxy info */
3890 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3891 ((curpx == defpx) ? "defaults" : curpx->id),
3892 curpx->conf.file, curpx->conf.line);
3893
3894 rs = find_tcpcheck_ruleset(b_orig(&trash));
3895 if (rs == NULL) {
3896 rs = create_tcpcheck_ruleset(b_orig(&trash));
3897 if (rs == NULL) {
3898 memprintf(errmsg, "out of memory.\n");
3899 goto error;
3900 }
3901 }
3902
3903 index = 0;
3904 if (!LIST_ISEMPTY(&rs->rules)) {
3905 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3906 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003907 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003908 }
3909
3910 cur_arg = 1;
3911 if (strcmp(args[cur_arg], "connect") == 0)
3912 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3913 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3914 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3915 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3916 else if (strcmp(args[cur_arg], "expect") == 0)
3917 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3918 else if (strcmp(args[cur_arg], "comment") == 0)
3919 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3920 else {
3921 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3922
3923 if (!kw) {
3924 action_kw_tcp_check_build_list(&trash);
3925 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3926 "%s%s. but got '%s'",
3927 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3928 goto error;
3929 }
3930 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3931 }
3932
3933 if (!chk) {
3934 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3935 goto error;
3936 }
3937 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3938
3939 /* No error: add the tcp-check rule in the list */
3940 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003941 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003942
3943 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3944 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3945 /* Use this ruleset if the proxy already has tcp-check enabled */
3946 curpx->tcpcheck_rules.list = &rs->rules;
3947 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3948 }
3949 else {
3950 /* mark this ruleset as unused for now */
3951 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3952 }
3953
3954 return ret;
3955
3956 error:
3957 free_tcpcheck(chk, 0);
3958 free_tcpcheck_ruleset(rs);
3959 return -1;
3960}
3961
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003962/* Parses the "http-check" proxy keyword */
3963static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003964 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003965 char **errmsg)
3966{
3967 struct tcpcheck_ruleset *rs = NULL;
3968 struct tcpcheck_rule *chk = NULL;
3969 int index, cur_arg, ret = 0;
3970
3971 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3972 ret = 1;
3973
3974 cur_arg = 1;
3975 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3976 /* enable a graceful server shutdown on an HTTP 404 response */
3977 curpx->options |= PR_O_DISABLE404;
3978 if (too_many_args(1, args, errmsg, NULL))
3979 goto error;
3980 goto out;
3981 }
3982 else if (strcmp(args[cur_arg], "send-state") == 0) {
3983 /* enable emission of the apparent state of a server in HTTP checks */
3984 curpx->options2 |= PR_O2_CHK_SNDST;
3985 if (too_many_args(1, args, errmsg, NULL))
3986 goto error;
3987 goto out;
3988 }
3989
3990 /* Deduce the ruleset name from the proxy info */
3991 chunk_printf(&trash, "*http-check-%s_%s-%d",
3992 ((curpx == defpx) ? "defaults" : curpx->id),
3993 curpx->conf.file, curpx->conf.line);
3994
3995 rs = find_tcpcheck_ruleset(b_orig(&trash));
3996 if (rs == NULL) {
3997 rs = create_tcpcheck_ruleset(b_orig(&trash));
3998 if (rs == NULL) {
3999 memprintf(errmsg, "out of memory.\n");
4000 goto error;
4001 }
4002 }
4003
4004 index = 0;
4005 if (!LIST_ISEMPTY(&rs->rules)) {
4006 chk = LIST_PREV(&rs->rules, typeof(chk), list);
4007 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4008 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004009 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004010 }
4011
4012 if (strcmp(args[cur_arg], "connect") == 0)
4013 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4014 else if (strcmp(args[cur_arg], "send") == 0)
4015 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4016 else if (strcmp(args[cur_arg], "expect") == 0)
4017 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4018 file, line, errmsg);
4019 else if (strcmp(args[cur_arg], "comment") == 0)
4020 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4021 else {
4022 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4023
4024 if (!kw) {
4025 action_kw_tcp_check_build_list(&trash);
4026 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4027 " 'send', 'expect'%s%s. but got '%s'",
4028 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4029 goto error;
4030 }
4031 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4032 }
4033
4034 if (!chk) {
4035 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4036 goto error;
4037 }
4038 ret = (*errmsg != NULL); /* Handle warning */
4039
4040 chk->index = index;
4041 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4042 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4043 /* Use this ruleset if the proxy already has http-check enabled */
4044 curpx->tcpcheck_rules.list = &rs->rules;
4045 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4046 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4047 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4048 curpx->tcpcheck_rules.list = NULL;
4049 goto error;
4050 }
4051 }
4052 else {
4053 /* mark this ruleset as unused for now */
4054 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004055 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004056 }
4057
4058 out:
4059 return ret;
4060
4061 error:
4062 free_tcpcheck(chk, 0);
4063 free_tcpcheck_ruleset(rs);
4064 return -1;
4065}
4066
4067/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004068int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004069 const char *file, int line)
4070{
4071 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4072 static char *redis_res = "+PONG\r\n";
4073
4074 struct tcpcheck_ruleset *rs = NULL;
4075 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4076 struct tcpcheck_rule *chk;
4077 char *errmsg = NULL;
4078 int err_code = 0;
4079
4080 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4081 err_code |= ERR_WARN;
4082
4083 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4084 goto out;
4085
4086 curpx->options2 &= ~PR_O2_CHK_ANY;
4087 curpx->options2 |= PR_O2_TCPCHK_CHK;
4088
4089 free_tcpcheck_vars(&rules->preset_vars);
4090 rules->list = NULL;
4091 rules->flags = 0;
4092
4093 rs = find_tcpcheck_ruleset("*redis-check");
4094 if (rs)
4095 goto ruleset_found;
4096
4097 rs = create_tcpcheck_ruleset("*redis-check");
4098 if (rs == NULL) {
4099 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4100 goto error;
4101 }
4102
4103 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4104 1, curpx, &rs->rules, file, line, &errmsg);
4105 if (!chk) {
4106 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4107 goto error;
4108 }
4109 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004110 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004111
4112 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4113 "error-status", "L7STS",
4114 "on-error", "%[res.payload(0,0),cut_crlf]",
4115 "on-success", "Redis server is ok",
4116 ""},
4117 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4118 if (!chk) {
4119 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4120 goto error;
4121 }
4122 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004123 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004124
4125 ruleset_found:
4126 rules->list = &rs->rules;
4127 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4128 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4129
4130 out:
4131 free(errmsg);
4132 return err_code;
4133
4134 error:
4135 free_tcpcheck_ruleset(rs);
4136 err_code |= ERR_ALERT | ERR_FATAL;
4137 goto out;
4138}
4139
4140
4141/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004142int 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 +01004143 const char *file, int line)
4144{
4145 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4146 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4147 *
4148 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4149 */
4150 static char sslv3_client_hello[] = {
4151 "16" /* ContentType : 0x16 = Handshake */
4152 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4153 "0079" /* ContentLength : 0x79 bytes after this one */
4154 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4155 "000075" /* HandshakeLength : 0x75 bytes after this one */
4156 "0300" /* Hello Version : 0x0300 = v3 */
4157 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4158 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4159 "00" /* Session ID length : empty (no session ID) */
4160 "004E" /* Cipher Suite Length : 78 bytes after this one */
4161 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4162 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4163 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4164 "000D" "000E" "000F" "0010" /* various bit lengths, */
4165 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4166 "0015" "0016" "0017" "0018"
4167 "0019" "001A" "001B" "002F"
4168 "0030" "0031" "0032" "0033"
4169 "0034" "0035" "0036" "0037"
4170 "0038" "0039" "003A"
4171 "01" /* Compression Length : 0x01 = 1 byte for types */
4172 "00" /* Compression Type : 0x00 = NULL compression */
4173 };
4174
4175 struct tcpcheck_ruleset *rs = NULL;
4176 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4177 struct tcpcheck_rule *chk;
4178 char *errmsg = NULL;
4179 int err_code = 0;
4180
4181 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4182 err_code |= ERR_WARN;
4183
4184 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4185 goto out;
4186
4187 curpx->options2 &= ~PR_O2_CHK_ANY;
4188 curpx->options2 |= PR_O2_TCPCHK_CHK;
4189
4190 free_tcpcheck_vars(&rules->preset_vars);
4191 rules->list = NULL;
4192 rules->flags = 0;
4193
4194 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4195 if (rs)
4196 goto ruleset_found;
4197
4198 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4199 if (rs == NULL) {
4200 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4201 goto error;
4202 }
4203
4204 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4205 1, curpx, &rs->rules, file, line, &errmsg);
4206 if (!chk) {
4207 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4208 goto error;
4209 }
4210 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004211 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004212
4213 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4214 "min-recv", "5", "ok-status", "L6OK",
4215 "error-status", "L6RSP", "tout-status", "L6TOUT",
4216 ""},
4217 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4218 if (!chk) {
4219 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4220 goto error;
4221 }
4222 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004223 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004224
4225 ruleset_found:
4226 rules->list = &rs->rules;
4227 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4228 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4229
4230 out:
4231 free(errmsg);
4232 return err_code;
4233
4234 error:
4235 free_tcpcheck_ruleset(rs);
4236 err_code |= ERR_ALERT | ERR_FATAL;
4237 goto out;
4238}
4239
4240/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004241int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004242 const char *file, int line)
4243{
4244 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4245
4246 struct tcpcheck_ruleset *rs = NULL;
4247 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4248 struct tcpcheck_rule *chk;
4249 struct tcpcheck_var *var = NULL;
4250 char *cmd = NULL, *errmsg = NULL;
4251 int err_code = 0;
4252
4253 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4254 err_code |= ERR_WARN;
4255
4256 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4257 goto out;
4258
4259 curpx->options2 &= ~PR_O2_CHK_ANY;
4260 curpx->options2 |= PR_O2_TCPCHK_CHK;
4261
4262 free_tcpcheck_vars(&rules->preset_vars);
4263 rules->list = NULL;
4264 rules->flags = 0;
4265
4266 cur_arg += 2;
4267 if (*args[cur_arg] && *args[cur_arg+1] &&
4268 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4269 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4270 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4271 if (cmd)
4272 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4273 }
4274 else {
4275 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4276 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4277 cmd = strdup("HELO localhost");
4278 }
4279
4280 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4281 if (cmd == NULL || var == NULL) {
4282 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4283 goto error;
4284 }
4285 var->data.type = SMP_T_STR;
4286 var->data.u.str.area = cmd;
4287 var->data.u.str.data = strlen(cmd);
4288 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004289 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004290 cmd = NULL;
4291 var = NULL;
4292
4293 rs = find_tcpcheck_ruleset("*smtp-check");
4294 if (rs)
4295 goto ruleset_found;
4296
4297 rs = create_tcpcheck_ruleset("*smtp-check");
4298 if (rs == NULL) {
4299 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4300 goto error;
4301 }
4302
4303 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4304 1, curpx, &rs->rules, file, line, &errmsg);
4305 if (!chk) {
4306 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4307 goto error;
4308 }
4309 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004310 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004311
4312 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4313 "min-recv", "4",
4314 "error-status", "L7RSP",
4315 "on-error", "%[res.payload(0,0),cut_crlf]",
4316 ""},
4317 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4318 if (!chk) {
4319 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4320 goto error;
4321 }
4322 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004323 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004324
4325 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4326 "min-recv", "4",
4327 "error-status", "L7STS",
4328 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4329 "status-code", "res.payload(0,3)",
4330 ""},
4331 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4332 if (!chk) {
4333 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4334 goto error;
4335 }
4336 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004337 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004338
4339 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4340 1, curpx, &rs->rules, file, line, &errmsg);
4341 if (!chk) {
4342 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4343 goto error;
4344 }
4345 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004346 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004347
Christopher Faulet2ec1ffa2022-09-21 14:42:47 +02004348 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 +01004349 "error-status", "L7STS",
4350 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4351 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4352 "status-code", "res.payload(0,3)",
4353 ""},
4354 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4355 if (!chk) {
4356 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4357 goto error;
4358 }
4359 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004360 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004361
wrightlaw9a8d8a32022-09-08 16:10:48 +01004362 /* Send an SMTP QUIT to ensure clean disconnect (issue 1812), and expect a 2xx response code */
4363
4364 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", "QUIT\r\n", ""},
4365 1, curpx, &rs->rules, file, line, &errmsg);
4366 if (!chk) {
4367 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4368 goto error;
4369 }
4370 chk->index = 5;
4371 LIST_APPEND(&rs->rules, &chk->list);
4372
4373 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4374 "min-recv", "4",
4375 "error-status", "L7STS",
4376 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4377 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4378 "status-code", "res.payload(0,3)",
4379 ""},
4380 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4381 if (!chk) {
4382 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4383 goto error;
4384 }
4385 chk->index = 6;
4386 LIST_APPEND(&rs->rules, &chk->list);
4387
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004388 ruleset_found:
4389 rules->list = &rs->rules;
4390 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4391 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4392
4393 out:
4394 free(errmsg);
4395 return err_code;
4396
4397 error:
4398 free(cmd);
4399 free(var);
4400 free_tcpcheck_vars(&rules->preset_vars);
4401 free_tcpcheck_ruleset(rs);
4402 err_code |= ERR_ALERT | ERR_FATAL;
4403 goto out;
4404}
4405
4406/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004407int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004408 const char *file, int line)
4409{
4410 static char pgsql_req[] = {
4411 "%[var(check.plen),htonl,hex]" /* The packet length*/
4412 "00030000" /* the version 3.0 */
4413 "7573657200" /* "user" key */
4414 "%[var(check.username),hex]00" /* the username */
4415 "00"
4416 };
4417
4418 struct tcpcheck_ruleset *rs = NULL;
4419 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4420 struct tcpcheck_rule *chk;
4421 struct tcpcheck_var *var = NULL;
4422 char *user = NULL, *errmsg = NULL;
4423 size_t packetlen = 0;
4424 int err_code = 0;
4425
4426 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4427 err_code |= ERR_WARN;
4428
4429 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4430 goto out;
4431
4432 curpx->options2 &= ~PR_O2_CHK_ANY;
4433 curpx->options2 |= PR_O2_TCPCHK_CHK;
4434
4435 free_tcpcheck_vars(&rules->preset_vars);
4436 rules->list = NULL;
4437 rules->flags = 0;
4438
4439 cur_arg += 2;
4440 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4441 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4442 file, line, args[0], args[1]);
4443 goto error;
4444 }
4445 if (strcmp(args[cur_arg], "user") == 0) {
4446 packetlen = 15 + strlen(args[cur_arg+1]);
4447 user = strdup(args[cur_arg+1]);
4448
4449 var = create_tcpcheck_var(ist("check.username"));
4450 if (user == NULL || var == NULL) {
4451 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4452 goto error;
4453 }
4454 var->data.type = SMP_T_STR;
4455 var->data.u.str.area = user;
4456 var->data.u.str.data = strlen(user);
4457 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004458 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004459 user = NULL;
4460 var = NULL;
4461
4462 var = create_tcpcheck_var(ist("check.plen"));
4463 if (var == NULL) {
4464 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4465 goto error;
4466 }
4467 var->data.type = SMP_T_SINT;
4468 var->data.u.sint = packetlen;
4469 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004470 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004471 var = NULL;
4472 }
4473 else {
4474 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4475 file, line, args[0], args[1]);
4476 goto error;
4477 }
4478
4479 rs = find_tcpcheck_ruleset("*pgsql-check");
4480 if (rs)
4481 goto ruleset_found;
4482
4483 rs = create_tcpcheck_ruleset("*pgsql-check");
4484 if (rs == NULL) {
4485 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4486 goto error;
4487 }
4488
4489 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4490 1, curpx, &rs->rules, file, line, &errmsg);
4491 if (!chk) {
4492 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4493 goto error;
4494 }
4495 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004496 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004497
4498 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4499 1, curpx, &rs->rules, file, line, &errmsg);
4500 if (!chk) {
4501 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4502 goto error;
4503 }
4504 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004505 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004506
4507 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4508 "min-recv", "5",
4509 "error-status", "L7RSP",
4510 "on-error", "%[res.payload(6,0)]",
4511 ""},
4512 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4513 if (!chk) {
4514 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4515 goto error;
4516 }
4517 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004518 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004519
4520 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4521 "min-recv", "9",
4522 "error-status", "L7STS",
4523 "on-success", "PostgreSQL server is ok",
4524 "on-error", "PostgreSQL unknown error",
4525 ""},
4526 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4527 if (!chk) {
4528 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4529 goto error;
4530 }
4531 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004532 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004533
4534 ruleset_found:
4535 rules->list = &rs->rules;
4536 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4537 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4538
4539 out:
4540 free(errmsg);
4541 return err_code;
4542
4543 error:
4544 free(user);
4545 free(var);
4546 free_tcpcheck_vars(&rules->preset_vars);
4547 free_tcpcheck_ruleset(rs);
4548 err_code |= ERR_ALERT | ERR_FATAL;
4549 goto out;
4550}
4551
4552
4553/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004554int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004555 const char *file, int line)
4556{
4557 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4558 * const char mysql40_client_auth_pkt[] = {
4559 * "\x0e\x00\x00" // packet length
4560 * "\x01" // packet number
4561 * "\x00\x00" // client capabilities
4562 * "\x00\x00\x01" // max packet
4563 * "haproxy\x00" // username (null terminated string)
4564 * "\x00" // filler (always 0x00)
4565 * "\x01\x00\x00" // packet length
4566 * "\x00" // packet number
4567 * "\x01" // COM_QUIT command
4568 * };
4569 */
4570 static char mysql40_rsname[] = "*mysql40-check";
4571 static char mysql40_req[] = {
4572 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4573 "0080" /* client capabilities */
4574 "000001" /* max packet */
4575 "%[var(check.username),hex]00" /* the username */
4576 "00" /* filler (always 0x00) */
4577 "010000" /* packet length*/
4578 "00" /* sequence ID */
4579 "01" /* COM_QUIT command */
4580 };
4581
4582 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4583 * const char mysql41_client_auth_pkt[] = {
4584 * "\x0e\x00\x00\" // packet length
4585 * "\x01" // packet number
4586 * "\x00\x00\x00\x00" // client capabilities
4587 * "\x00\x00\x00\x01" // max packet
4588 * "\x21" // character set (UTF-8)
4589 * char[23] // All zeroes
4590 * "haproxy\x00" // username (null terminated string)
4591 * "\x00" // filler (always 0x00)
4592 * "\x01\x00\x00" // packet length
4593 * "\x00" // packet number
4594 * "\x01" // COM_QUIT command
4595 * };
4596 */
4597 static char mysql41_rsname[] = "*mysql41-check";
4598 static char mysql41_req[] = {
4599 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4600 "00820000" /* client capabilities */
4601 "00800001" /* max packet */
4602 "21" /* character set (UTF-8) */
4603 "000000000000000000000000" /* 23 bytes, al zeroes */
4604 "0000000000000000000000"
4605 "%[var(check.username),hex]00" /* the username */
4606 "00" /* filler (always 0x00) */
4607 "010000" /* packet length*/
4608 "00" /* sequence ID */
4609 "01" /* COM_QUIT command */
4610 };
4611
4612 struct tcpcheck_ruleset *rs = NULL;
4613 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4614 struct tcpcheck_rule *chk;
4615 struct tcpcheck_var *var = NULL;
4616 char *mysql_rsname = "*mysql-check";
4617 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4618 int index = 0, err_code = 0;
4619
4620 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4621 err_code |= ERR_WARN;
4622
4623 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4624 goto out;
4625
4626 curpx->options2 &= ~PR_O2_CHK_ANY;
4627 curpx->options2 |= PR_O2_TCPCHK_CHK;
4628
4629 free_tcpcheck_vars(&rules->preset_vars);
4630 rules->list = NULL;
4631 rules->flags = 0;
4632
4633 cur_arg += 2;
4634 if (*args[cur_arg]) {
4635 int packetlen, userlen;
4636
4637 if (strcmp(args[cur_arg], "user") != 0) {
4638 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4639 file, line, args[0], args[1], args[cur_arg]);
4640 goto error;
4641 }
4642
4643 if (*(args[cur_arg+1]) == 0) {
4644 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4645 file, line, args[0], args[1], args[cur_arg]);
4646 goto error;
4647 }
4648
4649 hdr = calloc(4, sizeof(*hdr));
4650 user = strdup(args[cur_arg+1]);
4651 userlen = strlen(args[cur_arg+1]);
4652
4653 if (hdr == NULL || user == NULL) {
4654 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4655 goto error;
4656 }
4657
4658 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4659 packetlen = userlen + 7 + 27;
4660 mysql_req = mysql41_req;
4661 mysql_rsname = mysql41_rsname;
4662 }
4663 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4664 packetlen = userlen + 7;
4665 mysql_req = mysql40_req;
4666 mysql_rsname = mysql40_rsname;
4667 }
4668 else {
4669 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4670 file, line, args[cur_arg], args[cur_arg+2]);
4671 goto error;
4672 }
4673
4674 hdr[0] = (unsigned char)(packetlen & 0xff);
4675 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4676 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4677 hdr[3] = 1;
4678
4679 var = create_tcpcheck_var(ist("check.header"));
4680 if (var == NULL) {
4681 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4682 goto error;
4683 }
4684 var->data.type = SMP_T_STR;
4685 var->data.u.str.area = hdr;
4686 var->data.u.str.data = 4;
4687 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004688 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004689 hdr = NULL;
4690 var = NULL;
4691
4692 var = create_tcpcheck_var(ist("check.username"));
4693 if (var == NULL) {
4694 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4695 goto error;
4696 }
4697 var->data.type = SMP_T_STR;
4698 var->data.u.str.area = user;
4699 var->data.u.str.data = strlen(user);
4700 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004701 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004702 user = NULL;
4703 var = NULL;
4704 }
4705
4706 rs = find_tcpcheck_ruleset(mysql_rsname);
4707 if (rs)
4708 goto ruleset_found;
4709
4710 rs = create_tcpcheck_ruleset(mysql_rsname);
4711 if (rs == NULL) {
4712 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4713 goto error;
4714 }
4715
4716 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4717 1, curpx, &rs->rules, file, line, &errmsg);
4718 if (!chk) {
4719 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4720 goto error;
4721 }
4722 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004723 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004724
4725 if (mysql_req) {
4726 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4727 1, curpx, &rs->rules, file, line, &errmsg);
4728 if (!chk) {
4729 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4730 goto error;
4731 }
4732 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004733 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004734 }
4735
4736 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4737 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4738 if (!chk) {
4739 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4740 goto error;
4741 }
4742 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4743 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004744 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004745
4746 if (mysql_req) {
4747 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4748 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4749 if (!chk) {
4750 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4751 goto error;
4752 }
4753 chk->expect.custom = tcpcheck_mysql_expect_ok;
4754 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004755 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004756 }
4757
4758 ruleset_found:
4759 rules->list = &rs->rules;
4760 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4761 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4762
4763 out:
4764 free(errmsg);
4765 return err_code;
4766
4767 error:
4768 free(hdr);
4769 free(user);
4770 free(var);
4771 free_tcpcheck_vars(&rules->preset_vars);
4772 free_tcpcheck_ruleset(rs);
4773 err_code |= ERR_ALERT | ERR_FATAL;
4774 goto out;
4775}
4776
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004777int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004778 const char *file, int line)
4779{
4780 static char *ldap_req = "300C020101600702010304008000";
4781
4782 struct tcpcheck_ruleset *rs = NULL;
4783 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4784 struct tcpcheck_rule *chk;
4785 char *errmsg = NULL;
4786 int err_code = 0;
4787
4788 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4789 err_code |= ERR_WARN;
4790
4791 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4792 goto out;
4793
4794 curpx->options2 &= ~PR_O2_CHK_ANY;
4795 curpx->options2 |= PR_O2_TCPCHK_CHK;
4796
4797 free_tcpcheck_vars(&rules->preset_vars);
4798 rules->list = NULL;
4799 rules->flags = 0;
4800
4801 rs = find_tcpcheck_ruleset("*ldap-check");
4802 if (rs)
4803 goto ruleset_found;
4804
4805 rs = create_tcpcheck_ruleset("*ldap-check");
4806 if (rs == NULL) {
4807 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4808 goto error;
4809 }
4810
4811 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4812 1, curpx, &rs->rules, file, line, &errmsg);
4813 if (!chk) {
4814 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4815 goto error;
4816 }
4817 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004818 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004819
4820 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4821 "min-recv", "14",
4822 "on-error", "Not LDAPv3 protocol",
4823 ""},
4824 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4825 if (!chk) {
4826 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4827 goto error;
4828 }
4829 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004830 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004831
4832 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4833 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4834 if (!chk) {
4835 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4836 goto error;
4837 }
4838 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4839 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004840 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004841
4842 ruleset_found:
4843 rules->list = &rs->rules;
4844 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4845 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4846
4847 out:
4848 free(errmsg);
4849 return err_code;
4850
4851 error:
4852 free_tcpcheck_ruleset(rs);
4853 err_code |= ERR_ALERT | ERR_FATAL;
4854 goto out;
4855}
4856
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004857int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004858 const char *file, int line)
4859{
4860 struct tcpcheck_ruleset *rs = NULL;
4861 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4862 struct tcpcheck_rule *chk;
4863 char *spop_req = NULL;
4864 char *errmsg = NULL;
4865 int spop_len = 0, err_code = 0;
4866
4867 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4868 err_code |= ERR_WARN;
4869
4870 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4871 goto out;
4872
4873 curpx->options2 &= ~PR_O2_CHK_ANY;
4874 curpx->options2 |= PR_O2_TCPCHK_CHK;
4875
4876 free_tcpcheck_vars(&rules->preset_vars);
4877 rules->list = NULL;
4878 rules->flags = 0;
4879
4880
4881 rs = find_tcpcheck_ruleset("*spop-check");
4882 if (rs)
4883 goto ruleset_found;
4884
4885 rs = create_tcpcheck_ruleset("*spop-check");
4886 if (rs == NULL) {
4887 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4888 goto error;
4889 }
4890
4891 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4892 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4893 goto error;
4894 }
4895 chunk_reset(&trash);
4896 dump_binary(&trash, spop_req, spop_len);
4897 trash.area[trash.data] = '\0';
4898
4899 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4900 1, curpx, &rs->rules, file, line, &errmsg);
4901 if (!chk) {
4902 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4903 goto error;
4904 }
4905 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004906 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004907
4908 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4909 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4910 if (!chk) {
4911 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4912 goto error;
4913 }
4914 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4915 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004916 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004917
4918 ruleset_found:
4919 rules->list = &rs->rules;
4920 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4921 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4922
4923 out:
4924 free(spop_req);
4925 free(errmsg);
4926 return err_code;
4927
4928 error:
4929 free_tcpcheck_ruleset(rs);
4930 err_code |= ERR_ALERT | ERR_FATAL;
4931 goto out;
4932}
4933
4934
4935static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4936{
4937 struct tcpcheck_rule *chk = NULL;
4938 struct tcpcheck_http_hdr *hdr = NULL;
4939 char *meth = NULL, *uri = NULL, *vsn = NULL;
4940 char *hdrs, *body;
4941
4942 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4943 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004944 if (hdrs || body) {
Christopher Faulet4b5f3022022-09-05 09:05:17 +02004945 memprintf(errmsg, "hiding headers or body at the end of the version string is unsupported."
4946 "Use 'http-check send' directive instead.");
4947 goto error;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004948 }
4949
4950 chk = calloc(1, sizeof(*chk));
4951 if (!chk) {
4952 memprintf(errmsg, "out of memory");
4953 goto error;
4954 }
4955 chk->action = TCPCHK_ACT_SEND;
4956 chk->send.type = TCPCHK_SEND_HTTP;
4957 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4958 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4959 LIST_INIT(&chk->send.http.hdrs);
4960
4961 /* Copy the method, uri and version */
4962 if (*args[cur_arg]) {
4963 if (!*args[cur_arg+1])
4964 uri = args[cur_arg];
4965 else
4966 meth = args[cur_arg];
4967 }
4968 if (*args[cur_arg+1])
4969 uri = args[cur_arg+1];
4970 if (*args[cur_arg+2])
4971 vsn = args[cur_arg+2];
4972
4973 if (meth) {
4974 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4975 chk->send.http.meth.str.area = strdup(meth);
4976 chk->send.http.meth.str.data = strlen(meth);
4977 if (!chk->send.http.meth.str.area) {
4978 memprintf(errmsg, "out of memory");
4979 goto error;
4980 }
4981 }
4982 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004983 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004984 if (!isttest(chk->send.http.uri)) {
4985 memprintf(errmsg, "out of memory");
4986 goto error;
4987 }
4988 }
4989 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004990 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004991 if (!isttest(chk->send.http.vsn)) {
4992 memprintf(errmsg, "out of memory");
4993 goto error;
4994 }
4995 }
4996
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004997 return chk;
4998
4999 error:
5000 free_tcpcheck_http_hdr(hdr);
5001 free_tcpcheck(chk, 0);
5002 return NULL;
5003}
5004
5005/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005006int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005007 const char *file, int line)
5008{
5009 struct tcpcheck_ruleset *rs = NULL;
5010 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5011 struct tcpcheck_rule *chk;
5012 char *errmsg = NULL;
5013 int err_code = 0;
5014
5015 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5016 err_code |= ERR_WARN;
5017
5018 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5019 goto out;
5020
5021 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5022 if (!chk) {
5023 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5024 goto error;
5025 }
5026 if (errmsg) {
5027 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5028 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005029 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005030 }
5031
5032 no_request:
5033 curpx->options2 &= ~PR_O2_CHK_ANY;
5034 curpx->options2 |= PR_O2_TCPCHK_CHK;
5035
5036 free_tcpcheck_vars(&rules->preset_vars);
5037 rules->list = NULL;
5038 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5039
5040 /* Deduce the ruleset name from the proxy info */
5041 chunk_printf(&trash, "*http-check-%s_%s-%d",
5042 ((curpx == defpx) ? "defaults" : curpx->id),
5043 curpx->conf.file, curpx->conf.line);
5044
5045 rs = find_tcpcheck_ruleset(b_orig(&trash));
5046 if (rs == NULL) {
5047 rs = create_tcpcheck_ruleset(b_orig(&trash));
5048 if (rs == NULL) {
5049 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5050 goto error;
5051 }
5052 }
5053
5054 rules->list = &rs->rules;
5055 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5056 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5057 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5058 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5059 rules->list = NULL;
5060 goto error;
5061 }
5062
5063 out:
5064 free(errmsg);
5065 return err_code;
5066
5067 error:
5068 free_tcpcheck_ruleset(rs);
5069 free_tcpcheck(chk, 0);
5070 err_code |= ERR_ALERT | ERR_FATAL;
5071 goto out;
5072}
5073
5074/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005075int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005076 const char *file, int line)
5077{
5078 struct tcpcheck_ruleset *rs = NULL;
5079 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5080 int err_code = 0;
5081
5082 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5083 err_code |= ERR_WARN;
5084
5085 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5086 goto out;
5087
5088 curpx->options2 &= ~PR_O2_CHK_ANY;
5089 curpx->options2 |= PR_O2_TCPCHK_CHK;
5090
5091 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5092 /* If a tcp-check rulesset is already set, do nothing */
5093 if (rules->list)
5094 goto out;
5095
5096 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5097 * get it.
5098 */
5099 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5100 goto curpx_ruleset;
5101
5102 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5103 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5104 rs = find_tcpcheck_ruleset(b_orig(&trash));
5105 if (rs)
5106 goto ruleset_found;
5107 }
5108
5109 curpx_ruleset:
5110 /* Deduce the ruleset name from the proxy info */
5111 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5112 ((curpx == defpx) ? "defaults" : curpx->id),
5113 curpx->conf.file, curpx->conf.line);
5114
5115 rs = find_tcpcheck_ruleset(b_orig(&trash));
5116 if (rs == NULL) {
5117 rs = create_tcpcheck_ruleset(b_orig(&trash));
5118 if (rs == NULL) {
5119 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5120 goto error;
5121 }
5122 }
5123
5124 ruleset_found:
5125 free_tcpcheck_vars(&rules->preset_vars);
5126 rules->list = &rs->rules;
5127 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5128 rules->flags |= TCPCHK_RULES_TCP_CHK;
5129
5130 out:
5131 return err_code;
5132
5133 error:
5134 err_code |= ERR_ALERT | ERR_FATAL;
5135 goto out;
5136}
5137
Willy Tarreau51cd5952020-06-05 12:25:38 +02005138static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005139 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005140 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5141 { 0, NULL, NULL },
5142}};
5143
5144REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5145REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5146REGISTER_POST_DEINIT(deinit_tcpchecks);
5147INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);