blob: 6e995a0ea0f26ee51f3a0f04fd0b9eb7e728c031 [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>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version
13 * 2 of the License, or (at your option) any later version.
14 *
15 */
16
17#include <sys/resource.h>
18#include <sys/socket.h>
19#include <sys/types.h>
20#include <sys/wait.h>
21#include <netinet/in.h>
22#include <netinet/tcp.h>
23#include <arpa/inet.h>
24
25#include <ctype.h>
26#include <errno.h>
27#include <fcntl.h>
28#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>
Christopher Faulet1329f2a2021-12-16 17:32:56 +010042#include <haproxy/conn_stream.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020043#include <haproxy/errors.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020044#include <haproxy/global.h>
45#include <haproxy/h1.h>
46#include <haproxy/http.h>
47#include <haproxy/http_htx.h>
48#include <haproxy/htx.h>
49#include <haproxy/istbuf.h>
50#include <haproxy/list.h>
51#include <haproxy/log.h>
Christopher Faulet8a0e5f82021-09-16 16:01:09 +020052#include <haproxy/net_helper.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020053#include <haproxy/protocol.h>
54#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020055#include <haproxy/regex.h>
56#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020057#include <haproxy/server.h>
58#include <haproxy/ssl_sock.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020059#include <haproxy/task.h>
60#include <haproxy/tcpcheck.h>
Willy 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 */
825 const char *cs = NULL; /* maxconn */
826 const char *err = NULL; /* first error to report */
827 const char *wrn = NULL; /* first warning to report */
828 char *cmd, *p;
829
Christopher Faulet147b8c92021-04-10 09:00:38 +0200830 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
831
Willy Tarreau51cd5952020-06-05 12:25:38 +0200832 /* We're getting an agent check response. The agent could
833 * have been disabled in the mean time with a long check
834 * still pending. It is important that we ignore the whole
835 * response.
836 */
837 if (!(check->state & CHK_ST_ENABLED))
838 goto out;
839
840 /* The agent supports strings made of a single line ended by the
841 * first CR ('\r') or LF ('\n'). This line is composed of words
842 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
843 * line may optionally contained a description of a state change
844 * after a sharp ('#'), which is only considered if a health state
845 * is announced.
846 *
847 * Words may be composed of :
848 * - a numeric weight suffixed by the percent character ('%').
849 * - a health status among "up", "down", "stopped", and "fail".
850 * - an admin status among "ready", "drain", "maint".
851 *
852 * These words may appear in any order. If multiple words of the
853 * same category appear, the last one wins.
854 */
855
856 p = b_head(&check->bi);
857 while (*p && *p != '\n' && *p != '\r')
858 p++;
859
860 if (!*p) {
861 if (!last_read)
862 goto wait_more_data;
863
864 /* at least inform the admin that the agent is mis-behaving */
865 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
866 goto out;
867 }
868
869 *p = 0;
870 cmd = b_head(&check->bi);
871
872 while (*cmd) {
873 /* look for next word */
874 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
875 cmd++;
876 continue;
877 }
878
879 if (*cmd == '#') {
880 /* this is the beginning of a health status description,
881 * skip the sharp and blanks.
882 */
883 cmd++;
884 while (*cmd == '\t' || *cmd == ' ')
885 cmd++;
886 break;
887 }
888
889 /* find the end of the word so that we have a null-terminated
890 * word between <cmd> and <p>.
891 */
892 p = cmd + 1;
893 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
894 p++;
895 if (*p)
896 *p++ = 0;
897
898 /* first, health statuses */
899 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100900 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200901 status = HCHK_STATUS_L7OKD;
902 hs = cmd;
903 }
904 else if (strcasecmp(cmd, "down") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100905 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200906 status = HCHK_STATUS_L7STS;
907 hs = cmd;
908 }
909 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100910 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200911 status = HCHK_STATUS_L7STS;
912 hs = cmd;
913 }
914 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100915 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200916 status = HCHK_STATUS_L7STS;
917 hs = cmd;
918 }
919 /* admin statuses */
920 else if (strcasecmp(cmd, "ready") == 0) {
921 as = cmd;
922 }
923 else if (strcasecmp(cmd, "drain") == 0) {
924 as = cmd;
925 }
926 else if (strcasecmp(cmd, "maint") == 0) {
927 as = cmd;
928 }
929 /* try to parse a weight here and keep the last one */
930 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
931 ps = cmd;
932 }
933 /* try to parse a maxconn here */
934 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
935 cs = cmd;
936 }
937 else {
938 /* keep a copy of the first error */
939 if (!err)
940 err = cmd;
941 }
942 /* skip to next word */
943 cmd = p;
944 }
945 /* here, cmd points either to \0 or to the beginning of a
946 * description. Skip possible leading spaces.
947 */
948 while (*cmd == ' ' || *cmd == '\n')
949 cmd++;
950
951 /* First, update the admin status so that we avoid sending other
952 * possibly useless warnings and can also update the health if
953 * present after going back up.
954 */
955 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200956 if (strcasecmp(as, "drain") == 0) {
957 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200958 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200959 }
960 else if (strcasecmp(as, "maint") == 0) {
961 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200962 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200963 }
964 else {
965 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200966 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200967 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200968 }
969
970 /* now change weights */
971 if (ps) {
972 const char *msg;
973
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500974 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200975 msg = server_parse_weight_change_request(check->server, ps);
976 if (!wrn || !*wrn)
977 wrn = msg;
978 }
979
980 if (cs) {
981 const char *msg;
982
983 cs += strlen("maxconn:");
984
Christopher Faulet147b8c92021-04-10 09:00:38 +0200985 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury 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 Tarreau51cd5952020-06-05 12:25:38 +0200989 msg = server_parse_maxconn_change_request(check->server, cs);
990 if (!wrn || !*wrn)
991 wrn = msg;
992 }
993
994 /* and finally health status */
995 if (hs) {
996 /* We'll report some of the warnings and errors we have
997 * here. Down reports are critical, we leave them untouched.
998 * Lack of report, or report of 'UP' leaves the room for
999 * ERR first, then WARN.
1000 */
1001 const char *msg = cmd;
1002 struct buffer *t;
1003
1004 if (!*msg || status == HCHK_STATUS_L7OKD) {
1005 if (err && *err)
1006 msg = err;
1007 else if (wrn && *wrn)
1008 msg = wrn;
1009 }
1010
1011 t = get_trash_chunk();
1012 chunk_printf(t, "via agent : %s%s%s%s",
1013 hs, *msg ? " (" : "",
1014 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001015 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001016 set_server_check_status(check, status, t->area);
1017 }
1018 else if (err && *err) {
1019 /* No status change but we'd like to report something odd.
1020 * Just report the current state and copy the message.
1021 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001022 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001023 chunk_printf(&trash, "agent reports an error : %s", err);
1024 set_server_check_status(check, status/*check->status*/, trash.area);
1025 }
1026 else if (wrn && *wrn) {
1027 /* No status change but we'd like to report something odd.
1028 * Just report the current state and copy the message.
1029 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001030 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001031 chunk_printf(&trash, "agent warns : %s", wrn);
1032 set_server_check_status(check, status/*check->status*/, trash.area);
1033 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001034 else {
1035 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001036 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001037 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001038
1039 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001040 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001041 return ret;
1042
1043 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001044 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001045 ret = TCPCHK_EVAL_WAIT;
1046 goto out;
1047}
1048
1049/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1050 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1051 * TCPCHK_EVAL_STOP if an error occurred.
1052 */
1053enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1054{
1055 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1056 struct tcpcheck_connect *connect = &rule->connect;
1057 struct proxy *proxy = check->proxy;
1058 struct server *s = check->server;
1059 struct task *t = check->task;
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001060 struct connection *conn = cs_conn(check->cs);
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) {
1075 if (!(check->wait_list.events & SUB_RETRY_SEND))
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001076 conn->mux->subscribe(check->cs, SUB_RETRY_SEND, &check->wait_list);
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
1086 /* Note: here check->cs = cs = 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;
1101 TRACE_ERROR("conn-stream allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001102 goto out;
1103 }
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001104 cs_attach_endp(check->cs, &conn->obj_type, conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001105 tasklet_set_tid(check->wait_list.tasklet, tid);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001106 conn_set_owner(conn, check->sess, NULL);
1107
1108 /* Maybe there were an older connection we were waiting on */
1109 check->wait_list.events = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001110
1111 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001112 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001113 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001114 status = SF_ERR_RESOURCE;
1115 goto fail_check;
1116 }
1117
1118 /* connect to the connect rule addr if specified, otherwise the check
1119 * addr if specified on the server. otherwise, use the server addr (it
1120 * MUST exist at this step).
1121 */
1122 *conn->dst = (is_addr(&connect->addr)
1123 ? connect->addr
1124 : (is_addr(&check->addr) ? check->addr : s->addr));
Willy Tarreau14e7f292021-10-27 17:41:07 +02001125 proto = protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001126
1127 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001128 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001129 port = connect->port;
1130 if (!port && connect->port_expr) {
1131 struct sample *smp;
1132
1133 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1134 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1135 connect->port_expr, SMP_T_SINT);
1136 if (smp)
1137 port = smp->data.u.sint;
1138 }
1139 if (!port && is_inet_addr(&connect->addr))
1140 port = get_host_port(&connect->addr);
1141 if (!port && check->port)
1142 port = check->port;
1143 if (!port && is_inet_addr(&check->addr))
1144 port = get_host_port(&check->addr);
1145 if (!port) {
1146 /* The server MUST exist here */
1147 port = s->svc_port;
1148 }
1149 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001150 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001151
1152 xprt = ((connect->options & TCPCHK_OPT_SSL)
1153 ? xprt_get(XPRT_SSL)
1154 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1155
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001156 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001157 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001158 status = SF_ERR_RESOURCE;
1159 goto fail_check;
1160 }
1161
Christopher Fauletf7177272020-10-02 13:41:55 +02001162 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1163 conn->send_proxy_ofs = 1;
1164 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001165 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001166 }
1167 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1168 conn->send_proxy_ofs = 1;
1169 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001170 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001171 }
1172
1173 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1174 conn->send_proxy_ofs = 1;
1175 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001176 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001177 }
1178 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1179 conn->send_proxy_ofs = 1;
1180 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001181 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001182 }
1183
Willy Tarreau51cd5952020-06-05 12:25:38 +02001184 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001185 if (proto && proto->connect) {
1186 int flags = 0;
1187
1188 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1189 flags |= CONNECT_HAS_DATA;
1190 if (!next || next->action != TCPCHK_ACT_EXPECT)
1191 flags |= CONNECT_DELACK_ALWAYS;
1192 status = proto->connect(conn, flags);
1193 }
1194
1195 if (status != SF_ERR_NONE)
1196 goto fail_check;
1197
Christopher Faulet21ddc742020-07-01 15:26:14 +02001198 conn_set_private(conn);
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001199 conn->ctx = check->cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001200
Willy Tarreau51cd5952020-06-05 12:25:38 +02001201#ifdef USE_OPENSSL
1202 if (connect->sni)
1203 ssl_sock_set_servername(conn, connect->sni);
1204 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1205 ssl_sock_set_servername(conn, s->check.sni);
1206
1207 if (connect->alpn)
1208 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1209 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1210 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1211#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001212
Willy Tarreaue2226792022-04-11 18:04:33 +02001213 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER) && !(conn->flags & CO_FL_FDLESS)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001214 /* Some servers don't like reset on close */
Christopher Faulet897d6122021-12-17 17:28:35 +01001215 HA_ATOMIC_AND(&fdtab[conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001216 }
1217
1218 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1219 if (xprt_add_hs(conn) < 0)
1220 status = SF_ERR_RESOURCE;
1221 }
1222
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001223 if (conn_xprt_start(conn) < 0) {
1224 status = SF_ERR_RESOURCE;
1225 goto fail_check;
1226 }
1227
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001228 /* The mux may be initialized now if there isn't server attached to the
1229 * check (email alerts) or if there is a mux proto specified or if there
1230 * is no alpn.
1231 */
1232 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1233 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1234 const struct mux_ops *mux_ops;
1235
Christopher Faulet147b8c92021-04-10 09:00:38 +02001236 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001237 if (connect->mux_proto)
1238 mux_ops = connect->mux_proto->mux;
1239 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1240 mux_ops = check->mux_proto->mux;
1241 else {
1242 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1243 ? PROTO_MODE_HTTP
1244 : PROTO_MODE_TCP);
1245
1246 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1247 }
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001248 if (mux_ops && conn_install_mux(conn, mux_ops, check->cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001249 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001250 status = SF_ERR_INTERNAL;
1251 goto fail_check;
1252 }
1253 }
1254
Willy Tarreau51cd5952020-06-05 12:25:38 +02001255 fail_check:
1256 /* It can return one of :
1257 * - SF_ERR_NONE if everything's OK
1258 * - SF_ERR_SRVTO if there are no more servers
1259 * - SF_ERR_SRVCL if the connection was refused by the server
1260 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1261 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1262 * - SF_ERR_INTERNAL for any other purely internal errors
1263 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1264 * Note that we try to prevent the network stack from sending the ACK during the
1265 * connect() when a pure TCP check is used (without PROXY protocol).
1266 */
1267 switch (status) {
1268 case SF_ERR_NONE:
1269 /* we allow up to min(inter, timeout.connect) for a connection
1270 * to establish but only when timeout.check is set as it may be
1271 * to short for a full check otherwise
1272 */
1273 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1274
1275 if (proxy->timeout.check && proxy->timeout.connect) {
1276 int t_con = tick_add(now_ms, proxy->timeout.connect);
1277 t->expire = tick_first(t->expire, t_con);
1278 }
1279 break;
1280 case SF_ERR_SRVTO: /* ETIMEDOUT */
1281 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1282 case SF_ERR_PRXCOND:
1283 case SF_ERR_RESOURCE:
1284 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001285 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 +02001286 chk_report_conn_err(check, errno, 0);
1287 ret = TCPCHK_EVAL_STOP;
1288 goto out;
1289 }
1290
1291 /* don't do anything until the connection is established */
1292 if (conn->flags & CO_FL_WAIT_XPRT) {
1293 if (conn->mux) {
1294 if (next && next->action == TCPCHK_ACT_SEND)
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001295 conn->mux->subscribe(check->cs, SUB_RETRY_SEND, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001296 else
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001297 conn->mux->subscribe(check->cs, SUB_RETRY_RECV, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001298 }
1299 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001300 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001301 goto out;
1302 }
1303
1304 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001305 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001306 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001307 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001308 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001309
1310 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1311 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1312
Christopher Faulet147b8c92021-04-10 09:00:38 +02001313 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001314 return ret;
1315}
1316
1317/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1318 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1319 * TCPCHK_EVAL_STOP if an error occurred.
1320 */
1321enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1322{
1323 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1324 struct tcpcheck_send *send = &rule->send;
1325 struct conn_stream *cs = check->cs;
Christopher Faulet693b23b2022-02-28 09:09:05 +01001326 struct connection *conn = __cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001327 struct buffer *tmp = NULL;
1328 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001329 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001330
Christopher Faulet147b8c92021-04-10 09:00:38 +02001331 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1332
Christopher Fauletb381a502020-11-25 13:47:00 +01001333 if (check->state & CHK_ST_OUT_ALLOC) {
1334 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001335 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 +01001336 goto out;
1337 }
1338
1339 if (!check_get_buf(check, &check->bo)) {
1340 check->state |= CHK_ST_OUT_ALLOC;
1341 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001342 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 +01001343 goto out;
1344 }
1345
Christopher Faulet39066c22020-11-25 13:34:51 +01001346 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001347 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 +02001348 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 +01001349 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001350 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001351
Christopher Fauletb381a502020-11-25 13:47:00 +01001352 /* Always release input buffer when a new send is evaluated */
1353 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001354
1355 switch (send->type) {
1356 case TCPCHK_SEND_STRING:
1357 case TCPCHK_SEND_BINARY:
1358 if (istlen(send->data) >= b_size(&check->bo)) {
1359 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1360 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1361 tcpcheck_get_step_id(check, rule));
1362 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1363 ret = TCPCHK_EVAL_STOP;
1364 goto out;
1365 }
1366 b_putist(&check->bo, send->data);
1367 break;
1368 case TCPCHK_SEND_STRING_LF:
1369 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1370 if (!b_data(&check->bo))
1371 goto out;
1372 break;
1373 case TCPCHK_SEND_BINARY_LF: {
1374 int len = b_size(&check->bo);
1375
1376 tmp = alloc_trash_chunk();
1377 if (!tmp)
1378 goto error_lf;
1379 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1380 if (!b_data(tmp))
1381 goto out;
1382 tmp->area[tmp->data] = '\0';
1383 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1384 goto error_lf;
1385 check->bo.data = len;
1386 break;
1387 }
1388 case TCPCHK_SEND_HTTP: {
1389 struct htx_sl *sl;
1390 struct ist meth, uri, vsn, clen, body;
1391 unsigned int slflags = 0;
1392
1393 tmp = alloc_trash_chunk();
1394 if (!tmp)
1395 goto error_htx;
1396
1397 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1398 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1399 : http_known_methods[send->http.meth.meth]);
1400 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1401 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1402 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1403 }
1404 else
1405 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1406 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1407
1408 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1409 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1410 slflags |= HTX_SL_F_VER_11;
1411 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1412 if (!isttest(send->http.body))
1413 slflags |= HTX_SL_F_BODYLESS;
1414
1415 htx = htx_from_buf(&check->bo);
1416 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1417 if (!sl)
1418 goto error_htx;
1419 sl->info.req.meth = send->http.meth.meth;
1420 if (!http_update_host(htx, sl, uri))
1421 goto error_htx;
1422
1423 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1424 struct tcpcheck_http_hdr *hdr;
1425 struct ist hdr_value;
1426
1427 list_for_each_entry(hdr, &send->http.hdrs, list) {
1428 chunk_reset(tmp);
1429 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1430 if (!b_data(tmp))
1431 continue;
1432 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1433 if (!htx_add_header(htx, hdr->name, hdr_value))
1434 goto error_htx;
1435 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1436 if (!http_update_authority(htx, sl, hdr_value))
1437 goto error_htx;
1438 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001439 if (isteqi(hdr->name, ist("connection")))
1440 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001441 }
1442
1443 }
1444 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1445 chunk_reset(tmp);
1446 httpchk_build_status_header(check->server, tmp);
1447 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1448 goto error_htx;
1449 }
1450
1451
1452 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1453 chunk_reset(tmp);
1454 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1455 body = ist2(b_orig(tmp), b_data(tmp));
1456 }
1457 else
1458 body = send->http.body;
1459 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1460
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001461 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001462 !htx_add_header(htx, ist("Content-length"), clen))
1463 goto error_htx;
1464
1465
1466 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001467 (istlen(body) && !htx_add_data_atonce(htx, body)))
1468 goto error_htx;
1469
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001470 /* no more data are expected */
1471 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001472 htx_to_buf(htx, &check->bo);
1473 break;
1474 }
1475 case TCPCHK_SEND_UNDEF:
1476 /* Should never happen. */
1477 ret = TCPCHK_EVAL_STOP;
1478 goto out;
1479 };
1480
Christopher Faulet39066c22020-11-25 13:34:51 +01001481 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001482 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001483 if (conn->mux->snd_buf(cs, &check->bo,
1484 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1485 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1486 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001487 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 +02001488 goto out;
1489 }
1490 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001491 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Faulet897d6122021-12-17 17:28:35 +01001492 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001493 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001494 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001495 goto out;
1496 }
1497
1498 out:
1499 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001500 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1501 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001502
1503 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001504 return ret;
1505
1506 error_htx:
1507 if (htx) {
1508 htx_reset(htx);
1509 htx_to_buf(htx, &check->bo);
1510 }
1511 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1512 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001513 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 +02001514 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1515 ret = TCPCHK_EVAL_STOP;
1516 goto out;
1517
1518 error_lf:
1519 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1520 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001521 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 +02001522 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1523 ret = TCPCHK_EVAL_STOP;
1524 goto out;
1525
1526}
1527
1528/* Try to receive data before evaluating a tcp-check expect rule. Returns
1529 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1530 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1531 * TCPCHK_EVAL_STOP if an error occurred.
1532 */
1533enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1534{
1535 struct conn_stream *cs = check->cs;
Christopher Faulet693b23b2022-02-28 09:09:05 +01001536 struct connection *conn = __cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001537 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1538 size_t max, read, cur_read = 0;
1539 int is_empty;
1540 int read_poll = MAX_READ_POLL_LOOPS;
1541
Christopher Faulet147b8c92021-04-10 09:00:38 +02001542 TRACE_ENTER(CHK_EV_RX_DATA, check);
1543
1544 if (check->wait_list.events & SUB_RETRY_RECV) {
1545 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001546 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001547 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001548
1549 if (cs->flags & CS_FL_EOS)
1550 goto end_recv;
1551
Christopher Faulet147b8c92021-04-10 09:00:38 +02001552 if (check->state & CHK_ST_IN_ALLOC) {
1553 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001554 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001555 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001556
1557 if (!check_get_buf(check, &check->bi)) {
1558 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001559 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001560 goto wait_more_data;
1561 }
1562
Willy Tarreau51cd5952020-06-05 12:25:38 +02001563 /* errors on the connection and the conn-stream were already checked */
1564
1565 /* prepare to detect if the mux needs more room */
1566 cs->flags &= ~CS_FL_WANT_ROOM;
1567
1568 while ((cs->flags & CS_FL_RCV_MORE) ||
1569 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1570 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1571 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1572 cur_read += read;
1573 if (!read ||
1574 (cs->flags & CS_FL_WANT_ROOM) ||
1575 (--read_poll <= 0) ||
1576 (read < max && read >= global.tune.recv_enough))
1577 break;
1578 }
1579
1580 end_recv:
1581 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1582 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1583 /* Report network errors only if we got no other data. Otherwise
1584 * we'll let the upper layers decide whether the response is OK
1585 * or not. It is very common that an RST sent by the server is
1586 * reported as an error just after the last data chunk.
1587 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001588 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001589 goto stop;
1590 }
1591 if (!cur_read) {
Christopher Fauletd16e7dd2021-10-20 13:53:38 +02001592 if (cs->flags & CS_FL_EOI) {
1593 /* If EOI is set, it means there is a response or an error */
1594 goto out;
1595 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001596 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1597 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001598 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001599 goto wait_more_data;
1600 }
1601 if (is_empty) {
1602 int status;
1603
1604 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1605 tcpcheck_get_step_id(check, rule));
1606 if (rule->comment)
1607 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1608
Christopher Faulet147b8c92021-04-10 09:00:38 +02001609 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001610 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1611 set_server_check_status(check, status, trash.area);
1612 goto stop;
1613 }
1614 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001615 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001616
1617 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001618 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1619 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001620
1621 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001622 return ret;
1623
1624 stop:
1625 ret = TCPCHK_EVAL_STOP;
1626 goto out;
1627
1628 wait_more_data:
1629 ret = TCPCHK_EVAL_WAIT;
1630 goto out;
1631}
1632
1633/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1634 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1635 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1636 * error occurred.
1637 */
1638enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1639{
1640 struct htx *htx = htxbuf(&check->bi);
1641 struct htx_sl *sl;
1642 struct htx_blk *blk;
1643 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1644 struct tcpcheck_expect *expect = &rule->expect;
1645 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1646 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1647 struct ist desc = IST_NULL;
1648 int i, match, inverse;
1649
Christopher Faulet147b8c92021-04-10 09:00:38 +02001650 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1651
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001652 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001653
1654 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001655 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001656 status = HCHK_STATUS_L7RSP;
1657 goto error;
1658 }
1659
1660 if (htx_is_empty(htx)) {
1661 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001662 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001663 status = HCHK_STATUS_L7RSP;
1664 goto error;
1665 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001666 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001667 goto wait_more_data;
1668 }
1669
1670 sl = http_get_stline(htx);
1671 check->code = sl->info.res.status;
1672
1673 if (check->server &&
1674 (check->server->proxy->options & PR_O_DISABLE404) &&
1675 (check->server->next_state != SRV_ST_STOPPED) &&
1676 (check->code == 404)) {
1677 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001678 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001679 goto out;
1680 }
1681
1682 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1683 /* Make GCC happy ; initialize match to a failure state. */
1684 match = inverse;
1685 status = expect->err_status;
1686
1687 switch (expect->type) {
1688 case TCPCHK_EXPECT_HTTP_STATUS:
1689 match = 0;
1690 for (i = 0; i < expect->codes.num; i++) {
1691 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1692 sl->info.res.status <= expect->codes.codes[i][1]) {
1693 match = 1;
1694 break;
1695 }
1696 }
1697
1698 /* Set status and description in case of error */
1699 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1700 if (LIST_ISEMPTY(&expect->onerror_fmt))
1701 desc = htx_sl_res_reason(sl);
1702 break;
1703 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1704 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1705
1706 /* Set status and description in case of error */
1707 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1708 if (LIST_ISEMPTY(&expect->onerror_fmt))
1709 desc = htx_sl_res_reason(sl);
1710 break;
1711
1712 case TCPCHK_EXPECT_HTTP_HEADER: {
1713 struct http_hdr_ctx ctx;
1714 struct ist npat, vpat, value;
1715 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1716
1717 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1718 nbuf = alloc_trash_chunk();
1719 if (!nbuf) {
1720 status = HCHK_STATUS_L7RSP;
1721 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001722 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001723 goto error;
1724 }
1725 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1726 if (!b_data(nbuf)) {
1727 status = HCHK_STATUS_L7RSP;
1728 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001729 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001730 goto error;
1731 }
1732 npat = ist2(b_orig(nbuf), b_data(nbuf));
1733 }
1734 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1735 npat = expect->hdr.name;
1736
1737 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1738 vbuf = alloc_trash_chunk();
1739 if (!vbuf) {
1740 status = HCHK_STATUS_L7RSP;
1741 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001742 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001743 goto error;
1744 }
1745 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1746 if (!b_data(vbuf)) {
1747 status = HCHK_STATUS_L7RSP;
1748 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001749 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001750 goto error;
1751 }
1752 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1753 }
1754 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1755 vpat = expect->hdr.value;
1756
1757 match = 0;
1758 ctx.blk = NULL;
1759 while (1) {
1760 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1761 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1762 if (!http_find_str_header(htx, npat, &ctx, full))
1763 goto end_of_match;
1764 break;
1765 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1766 if (!http_find_pfx_header(htx, npat, &ctx, full))
1767 goto end_of_match;
1768 break;
1769 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1770 if (!http_find_sfx_header(htx, npat, &ctx, full))
1771 goto end_of_match;
1772 break;
1773 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1774 if (!http_find_sub_header(htx, npat, &ctx, full))
1775 goto end_of_match;
1776 break;
1777 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1778 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1779 goto end_of_match;
1780 break;
1781 default:
1782 /* should never happen */
1783 goto end_of_match;
1784 }
1785
1786 /* A header has matched the name pattern, let's test its
1787 * value now (always defined from there). If there is no
1788 * value pattern, it is a good match.
1789 */
1790
1791 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1792 match = 1;
1793 goto end_of_match;
1794 }
1795
1796 value = ctx.value;
1797 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1798 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1799 if (isteq(value, vpat)) {
1800 match = 1;
1801 goto end_of_match;
1802 }
1803 break;
1804 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1805 if (istlen(value) < istlen(vpat))
1806 break;
1807 value = ist2(istptr(value), istlen(vpat));
1808 if (isteq(value, vpat)) {
1809 match = 1;
1810 goto end_of_match;
1811 }
1812 break;
1813 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1814 if (istlen(value) < istlen(vpat))
1815 break;
Tim Duesterhus4c8f75f2021-11-06 15:14:44 +01001816 value = ist2(istend(value) - istlen(vpat), istlen(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001817 if (isteq(value, vpat)) {
1818 match = 1;
1819 goto end_of_match;
1820 }
1821 break;
1822 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1823 if (isttest(istist(value, vpat))) {
1824 match = 1;
1825 goto end_of_match;
1826 }
1827 break;
1828 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1829 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1830 match = 1;
1831 goto end_of_match;
1832 }
1833 break;
1834 }
1835 }
1836
1837 end_of_match:
1838 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1839 if (LIST_ISEMPTY(&expect->onerror_fmt))
1840 desc = htx_sl_res_reason(sl);
1841 break;
1842 }
1843
1844 case TCPCHK_EXPECT_HTTP_BODY:
1845 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1846 case TCPCHK_EXPECT_HTTP_BODY_LF:
1847 match = 0;
1848 chunk_reset(&trash);
1849 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1850 enum htx_blk_type type = htx_get_blk_type(blk);
1851
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001852 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001853 break;
1854 if (type == HTX_BLK_DATA) {
1855 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1856 break;
1857 }
1858 }
1859
1860 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001861 if (!last_read) {
1862 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001863 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001864 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001865 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1866 if (LIST_ISEMPTY(&expect->onerror_fmt))
1867 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001868 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001869 goto error;
1870 }
1871
1872 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1873 tmp = alloc_trash_chunk();
1874 if (!tmp) {
1875 status = HCHK_STATUS_L7RSP;
1876 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001877 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001878 goto error;
1879 }
1880 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1881 if (!b_data(tmp)) {
1882 status = HCHK_STATUS_L7RSP;
1883 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001884 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001885 goto error;
1886 }
1887 }
1888
1889 if (!last_read &&
1890 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1891 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1892 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1893 ret = TCPCHK_EVAL_WAIT;
1894 goto out;
1895 }
1896
1897 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1898 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1899 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1900 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1901 else
1902 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1903
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001904 /* Wait for more data on mismatch only if no minimum is defined (-1),
1905 * otherwise the absence of match is already conclusive.
1906 */
1907 if (!match && !last_read && (expect->min_recv == -1)) {
1908 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001909 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001910 goto out;
1911 }
1912
Willy Tarreau51cd5952020-06-05 12:25:38 +02001913 /* Set status and description in case of error */
1914 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1915 if (LIST_ISEMPTY(&expect->onerror_fmt))
1916 desc = (inverse
1917 ? ist("HTTP check matched unwanted content")
1918 : ist("HTTP content check did not match"));
1919 break;
1920
1921
1922 default:
1923 /* should never happen */
1924 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1925 goto error;
1926 }
1927
Christopher Faulet147b8c92021-04-10 09:00:38 +02001928 if (!(match ^ inverse)) {
1929 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001930 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001931 }
1932
1933 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001934
1935 out:
1936 free_trash_chunk(tmp);
1937 free_trash_chunk(nbuf);
1938 free_trash_chunk(vbuf);
1939 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001940 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001941 return ret;
1942
1943 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001944 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001945 ret = TCPCHK_EVAL_STOP;
1946 msg = alloc_trash_chunk();
1947 if (msg)
1948 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1949 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1950 goto out;
1951
1952 wait_more_data:
1953 ret = TCPCHK_EVAL_WAIT;
1954 goto out;
1955}
1956
1957/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1958 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1959 * if an error occurred.
1960 */
1961enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1962{
1963 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1964 struct tcpcheck_expect *expect = &rule->expect;
1965 struct buffer *msg = NULL, *tmp = NULL;
1966 struct ist desc = IST_NULL;
1967 enum healthcheck_status status;
1968 int match, inverse;
1969
Christopher Faulet147b8c92021-04-10 09:00:38 +02001970 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1971
Willy Tarreau51cd5952020-06-05 12:25:38 +02001972 last_read |= b_full(&check->bi);
1973
1974 /* The current expect might need more data than the previous one, check again
1975 * that the minimum amount data required to match is respected.
1976 */
1977 if (!last_read) {
1978 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1979 (b_data(&check->bi) < istlen(expect->data))) {
1980 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001981 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001982 goto out;
1983 }
1984 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1985 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001986 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001987 goto out;
1988 }
1989 }
1990
1991 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1992 /* Make GCC happy ; initialize match to a failure state. */
1993 match = inverse;
1994 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1995
1996 switch (expect->type) {
1997 case TCPCHK_EXPECT_STRING:
1998 case TCPCHK_EXPECT_BINARY:
1999 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2000 break;
2001 case TCPCHK_EXPECT_STRING_REGEX:
2002 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2003 break;
2004
2005 case TCPCHK_EXPECT_BINARY_REGEX:
2006 chunk_reset(&trash);
2007 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2008 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2009 break;
2010
2011 case TCPCHK_EXPECT_STRING_LF:
2012 case TCPCHK_EXPECT_BINARY_LF:
2013 match = 0;
2014 tmp = alloc_trash_chunk();
2015 if (!tmp) {
2016 status = HCHK_STATUS_L7RSP;
2017 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002018 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002019 goto error;
2020 }
2021 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2022 if (!b_data(tmp)) {
2023 status = HCHK_STATUS_L7RSP;
2024 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002025 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002026 goto error;
2027 }
2028 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2029 int len = tmp->data;
2030 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2031 status = HCHK_STATUS_L7RSP;
2032 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002033 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002034 goto error;
2035 }
2036 tmp->data = len;
2037 }
2038 if (b_data(&check->bi) < tmp->data) {
2039 if (!last_read) {
2040 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002041 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002042 goto out;
2043 }
2044 break;
2045 }
2046 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2047 break;
2048
2049 case TCPCHK_EXPECT_CUSTOM:
2050 if (expect->custom)
2051 ret = expect->custom(check, rule, last_read);
2052 goto out;
2053 default:
2054 /* Should never happen. */
2055 ret = TCPCHK_EVAL_STOP;
2056 goto out;
2057 }
2058
2059
2060 /* Wait for more data on mismatch only if no minimum is defined (-1),
2061 * otherwise the absence of match is already conclusive.
2062 */
2063 if (!match && !last_read && (expect->min_recv == -1)) {
2064 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002065 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002066 goto out;
2067 }
2068
2069 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002070 if (match ^ inverse) {
2071 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002072 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002073 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002074
2075 error:
2076 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002077 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002078 ret = TCPCHK_EVAL_STOP;
2079 msg = alloc_trash_chunk();
2080 if (msg)
2081 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2082 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2083 free_trash_chunk(msg);
2084
2085 out:
2086 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002087 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002088 return ret;
2089}
2090
2091/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2092 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2093 * waits.
2094 */
2095enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2096{
2097 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2098 struct act_rule *act_rule;
2099 enum act_return act_ret;
2100
2101 act_rule =rule->action_kw.rule;
2102 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2103 if (act_ret != ACT_RET_CONT) {
2104 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2105 tcpcheck_get_step_id(check, rule));
2106 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2107 ret = TCPCHK_EVAL_STOP;
2108 }
2109
2110 return ret;
2111}
2112
2113/* Executes a tcp-check ruleset. Note that this is called both from the
2114 * connection's wake() callback and from the check scheduling task. It returns
2115 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2116 * presenting the risk of an fd replacement.
2117 *
2118 * Please do NOT place any return statement in this function and only leave
2119 * via the out_end_tcpcheck label after setting retcode.
2120 */
2121int tcpcheck_main(struct check *check)
2122{
2123 struct tcpcheck_rule *rule;
2124 struct conn_stream *cs = check->cs;
2125 struct connection *conn = cs_conn(cs);
2126 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002127 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002128 enum tcpcheck_eval_ret eval_ret;
2129
2130 /* here, we know that the check is complete or that it failed */
2131 if (check->result != CHK_RES_UNKNOWN)
2132 goto out;
2133
Christopher Faulet147b8c92021-04-10 09:00:38 +02002134 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2135
Willy Tarreau51cd5952020-06-05 12:25:38 +02002136 /* Note: the conn-stream and the connection may only be undefined before
2137 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002138 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002139 */
2140
2141 /* 1- check for connection error, if any */
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002142 if ((conn && conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002143 goto out_end_tcpcheck;
2144
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002145 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002146 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002147 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002148 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002149 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2150 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002151
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002152 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002153 * tcp-check variables */
2154 else {
2155 struct tcpcheck_var *var;
2156
2157 /* First evaluation, create a session */
2158 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2159 if (!check->sess) {
2160 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002161 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002162 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2163 goto out_end_tcpcheck;
2164 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002165 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002166 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2167
2168 /* Preset tcp-check variables */
2169 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2170 struct sample smp;
2171
2172 memset(&smp, 0, sizeof(smp));
2173 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2174 smp.data = var->data;
2175 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2176 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002177 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002178 }
2179
2180 /* Now evaluate the tcp-check rules */
2181
2182 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2183 check->code = 0;
2184 switch (rule->action) {
2185 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002186 /* Not the first connection, release it first */
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002187 if (cs_conn(cs) && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002188 check->state |= CHK_ST_CLOSE_CONN;
2189 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002190 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002191
2192 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002193
2194 /* We are still waiting the connection gets closed */
2195 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002196 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002197 eval_ret = TCPCHK_EVAL_WAIT;
2198 break;
2199 }
2200
Christopher Faulet147b8c92021-04-10 09:00:38 +02002201 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002202 eval_ret = tcpcheck_eval_connect(check, rule);
2203
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002204 /* Refresh connection */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002205 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002206 last_read = 0;
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002207 must_read = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002208 break;
2209 case TCPCHK_ACT_SEND:
2210 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002211 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002212 eval_ret = tcpcheck_eval_send(check, rule);
2213 must_read = 1;
2214 break;
2215 case TCPCHK_ACT_EXPECT:
2216 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002217 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002218 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002219 eval_ret = tcpcheck_eval_recv(check, rule);
2220 if (eval_ret == TCPCHK_EVAL_STOP)
2221 goto out_end_tcpcheck;
2222 else if (eval_ret == TCPCHK_EVAL_WAIT)
2223 goto out;
2224 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2225 must_read = 0;
2226 }
2227
2228 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2229 ? tcpcheck_eval_expect_http(check, rule, last_read)
2230 : tcpcheck_eval_expect(check, rule, last_read));
2231
2232 if (eval_ret == TCPCHK_EVAL_WAIT) {
2233 check->current_step = rule->expect.head;
2234 if (!(check->wait_list.events & SUB_RETRY_RECV))
2235 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2236 }
2237 break;
2238 case TCPCHK_ACT_ACTION_KW:
2239 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002240 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002241 eval_ret = tcpcheck_eval_action_kw(check, rule);
2242 break;
2243 default:
2244 /* Otherwise, just go to the next one and don't update
2245 * the current step
2246 */
2247 eval_ret = TCPCHK_EVAL_CONTINUE;
2248 break;
2249 }
2250
2251 switch (eval_ret) {
2252 case TCPCHK_EVAL_CONTINUE:
2253 break;
2254 case TCPCHK_EVAL_WAIT:
2255 goto out;
2256 case TCPCHK_EVAL_STOP:
2257 goto out_end_tcpcheck;
2258 }
2259 }
2260
2261 /* All rules was evaluated */
2262 if (check->current_step) {
2263 rule = check->current_step;
2264
Christopher Faulet147b8c92021-04-10 09:00:38 +02002265 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2266
Willy Tarreau51cd5952020-06-05 12:25:38 +02002267 if (rule->action == TCPCHK_ACT_EXPECT) {
2268 struct buffer *msg;
2269 enum healthcheck_status status;
2270
2271 if (check->server &&
2272 (check->server->proxy->options & PR_O_DISABLE404) &&
2273 (check->server->next_state != SRV_ST_STOPPED) &&
2274 (check->code == 404)) {
2275 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002276 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002277 goto out_end_tcpcheck;
2278 }
2279
2280 msg = alloc_trash_chunk();
2281 if (msg)
2282 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2283 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2284 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2285 free_trash_chunk(msg);
2286 }
2287 else if (rule->action == TCPCHK_ACT_CONNECT) {
2288 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2289 enum healthcheck_status status = HCHK_STATUS_L4OK;
2290#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002291 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002292 status = HCHK_STATUS_L6OK;
2293#endif
2294 set_server_check_status(check, status, msg);
2295 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002296 else
2297 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002298 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002299 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002300 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002301 }
2302 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002303
2304 out_end_tcpcheck:
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002305 if ((conn && conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002306 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002307 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002308 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002309
Christopher Fauletb381a502020-11-25 13:47:00 +01002310 /* the tcpcheck is finished, release in/out buffer now */
2311 check_release_buf(check, &check->bi);
2312 check_release_buf(check, &check->bo);
2313
Willy Tarreau51cd5952020-06-05 12:25:38 +02002314 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002315 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002316 return retcode;
2317}
2318
Willy Tarreaua631b862022-03-02 14:54:44 +01002319void tcp_check_keywords_register(struct action_kw_list *kw_list)
2320{
2321 LIST_APPEND(&tcp_check_keywords.list, &kw_list->list);
2322}
Willy Tarreau51cd5952020-06-05 12:25:38 +02002323
2324/**************************************************************************/
2325/******************* Internals to parse tcp-check rules *******************/
2326/**************************************************************************/
2327struct action_kw_list tcp_check_keywords = {
2328 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2329};
2330
2331/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2332 * returned on error.
2333 */
2334struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2335 struct list *rules, struct action_kw *kw,
2336 const char *file, int line, char **errmsg)
2337{
2338 struct tcpcheck_rule *chk = NULL;
2339 struct act_rule *actrule = NULL;
2340
Willy Tarreaud535f802021-10-11 08:49:26 +02002341 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002342 if (!actrule) {
2343 memprintf(errmsg, "out of memory");
2344 goto error;
2345 }
2346 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002347
2348 cur_arg++;
2349 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2350 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2351 goto error;
2352 }
2353
2354 chk = calloc(1, sizeof(*chk));
2355 if (!chk) {
2356 memprintf(errmsg, "out of memory");
2357 goto error;
2358 }
2359 chk->action = TCPCHK_ACT_ACTION_KW;
2360 chk->action_kw.rule = actrule;
2361 return chk;
2362
2363 error:
2364 free(actrule);
2365 return NULL;
2366}
2367
2368/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2369 * returned on error.
2370 */
2371struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2372 const char *file, int line, char **errmsg)
2373{
2374 struct tcpcheck_rule *chk = NULL;
2375 struct sockaddr_storage *sk = NULL;
2376 char *comment = NULL, *sni = NULL, *alpn = NULL;
2377 struct sample_expr *port_expr = NULL;
2378 const struct mux_proto_list *mux_proto = NULL;
2379 unsigned short conn_opts = 0;
2380 long port = 0;
2381 int alpn_len = 0;
2382
2383 list_for_each_entry(chk, rules, list) {
2384 if (chk->action == TCPCHK_ACT_CONNECT)
2385 break;
2386 if (chk->action == TCPCHK_ACT_COMMENT ||
2387 chk->action == TCPCHK_ACT_ACTION_KW ||
2388 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2389 continue;
2390
2391 memprintf(errmsg, "first step MUST also be a 'connect', "
2392 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2393 "when there is a 'connect' step in the tcp-check ruleset");
2394 goto error;
2395 }
2396
2397 cur_arg++;
2398 while (*(args[cur_arg])) {
2399 if (strcmp(args[cur_arg], "default") == 0)
2400 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2401 else if (strcmp(args[cur_arg], "addr") == 0) {
2402 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002403
2404 if (!*(args[cur_arg+1])) {
2405 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2406 goto error;
2407 }
2408
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002409 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2410 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002411 if (!sk) {
2412 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2413 goto error;
2414 }
2415
Willy Tarreau51cd5952020-06-05 12:25:38 +02002416 cur_arg++;
2417 }
2418 else if (strcmp(args[cur_arg], "port") == 0) {
2419 const char *p, *end;
2420
2421 if (!*(args[cur_arg+1])) {
2422 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2423 goto error;
2424 }
2425 cur_arg++;
2426
2427 port = 0;
2428 release_sample_expr(port_expr);
2429 p = args[cur_arg]; end = p + strlen(p);
2430 port = read_uint(&p, end);
2431 if (p != end) {
2432 int idx = 0;
2433
2434 px->conf.args.ctx = ARGC_SRV;
2435 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002436 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002437
2438 if (!port_expr) {
2439 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2440 goto error;
2441 }
2442 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2443 memprintf(errmsg, "error detected while parsing port expression : "
2444 " fetch method '%s' extracts information from '%s', "
2445 "none of which is available here.\n",
2446 args[cur_arg], sample_src_names(port_expr->fetch->use));
2447 goto error;
2448 }
2449 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2450 }
2451 else if (port > 65535 || port < 1) {
2452 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2453 args[cur_arg]);
2454 goto error;
2455 }
2456 }
2457 else if (strcmp(args[cur_arg], "proto") == 0) {
2458 if (!*(args[cur_arg+1])) {
2459 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2460 goto error;
2461 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002462 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002463 if (!mux_proto) {
2464 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2465 goto error;
2466 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002467
2468 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2469 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2470 goto error;
2471 }
2472 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2473 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2474 goto error;
2475 }
2476
Willy Tarreau51cd5952020-06-05 12:25:38 +02002477 cur_arg++;
2478 }
2479 else if (strcmp(args[cur_arg], "comment") == 0) {
2480 if (!*(args[cur_arg+1])) {
2481 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2482 goto error;
2483 }
2484 cur_arg++;
2485 free(comment);
2486 comment = strdup(args[cur_arg]);
2487 if (!comment) {
2488 memprintf(errmsg, "out of memory");
2489 goto error;
2490 }
2491 }
2492 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2493 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2494 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2495 conn_opts |= TCPCHK_OPT_SOCKS4;
2496 else if (strcmp(args[cur_arg], "linger") == 0)
2497 conn_opts |= TCPCHK_OPT_LINGER;
2498#ifdef USE_OPENSSL
2499 else if (strcmp(args[cur_arg], "ssl") == 0) {
2500 px->options |= PR_O_TCPCHK_SSL;
2501 conn_opts |= TCPCHK_OPT_SSL;
2502 }
2503 else if (strcmp(args[cur_arg], "sni") == 0) {
2504 if (!*(args[cur_arg+1])) {
2505 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2506 goto error;
2507 }
2508 cur_arg++;
2509 free(sni);
2510 sni = strdup(args[cur_arg]);
2511 if (!sni) {
2512 memprintf(errmsg, "out of memory");
2513 goto error;
2514 }
2515 }
2516 else if (strcmp(args[cur_arg], "alpn") == 0) {
2517#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2518 free(alpn);
2519 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2520 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2521 goto error;
2522 }
2523 cur_arg++;
2524#else
2525 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2526 goto error;
2527#endif
2528 }
2529#endif /* USE_OPENSSL */
2530
2531 else {
2532 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2533#ifdef USE_OPENSSL
2534 ", 'ssl', 'sni', 'alpn'"
2535#endif /* USE_OPENSSL */
2536 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2537 args[cur_arg]);
2538 goto error;
2539 }
2540 cur_arg++;
2541 }
2542
2543 chk = calloc(1, sizeof(*chk));
2544 if (!chk) {
2545 memprintf(errmsg, "out of memory");
2546 goto error;
2547 }
2548 chk->action = TCPCHK_ACT_CONNECT;
2549 chk->comment = comment;
2550 chk->connect.port = port;
2551 chk->connect.options = conn_opts;
2552 chk->connect.sni = sni;
2553 chk->connect.alpn = alpn;
2554 chk->connect.alpn_len= alpn_len;
2555 chk->connect.port_expr= port_expr;
2556 chk->connect.mux_proto= mux_proto;
2557 if (sk)
2558 chk->connect.addr = *sk;
2559 return chk;
2560
2561 error:
2562 free(alpn);
2563 free(sni);
2564 free(comment);
2565 release_sample_expr(port_expr);
2566 return NULL;
2567}
2568
2569/* Parses and creates a tcp-check send rule. NULL is returned on error */
2570struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2571 const char *file, int line, char **errmsg)
2572{
2573 struct tcpcheck_rule *chk = NULL;
2574 char *comment = NULL, *data = NULL;
2575 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2576
2577 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2578 type = TCPCHK_SEND_BINARY_LF;
2579 else if (strcmp(args[cur_arg], "send-binary") == 0)
2580 type = TCPCHK_SEND_BINARY;
2581 else if (strcmp(args[cur_arg], "send-lf") == 0)
2582 type = TCPCHK_SEND_STRING_LF;
2583 else if (strcmp(args[cur_arg], "send") == 0)
2584 type = TCPCHK_SEND_STRING;
2585
2586 if (!*(args[cur_arg+1])) {
2587 memprintf(errmsg, "'%s' expects a %s as argument",
2588 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2589 goto error;
2590 }
2591
2592 data = args[cur_arg+1];
2593
2594 cur_arg += 2;
2595 while (*(args[cur_arg])) {
2596 if (strcmp(args[cur_arg], "comment") == 0) {
2597 if (!*(args[cur_arg+1])) {
2598 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2599 goto error;
2600 }
2601 cur_arg++;
2602 free(comment);
2603 comment = strdup(args[cur_arg]);
2604 if (!comment) {
2605 memprintf(errmsg, "out of memory");
2606 goto error;
2607 }
2608 }
2609 else {
2610 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2611 args[cur_arg]);
2612 goto error;
2613 }
2614 cur_arg++;
2615 }
2616
2617 chk = calloc(1, sizeof(*chk));
2618 if (!chk) {
2619 memprintf(errmsg, "out of memory");
2620 goto error;
2621 }
2622 chk->action = TCPCHK_ACT_SEND;
2623 chk->comment = comment;
2624 chk->send.type = type;
2625
2626 switch (chk->send.type) {
2627 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002628 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002629 if (!isttest(chk->send.data)) {
2630 memprintf(errmsg, "out of memory");
2631 goto error;
2632 }
2633 break;
2634 case TCPCHK_SEND_BINARY: {
2635 int len = chk->send.data.len;
2636 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2637 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2638 goto error;
2639 }
2640 chk->send.data.len = len;
2641 break;
2642 }
2643 case TCPCHK_SEND_STRING_LF:
2644 case TCPCHK_SEND_BINARY_LF:
2645 LIST_INIT(&chk->send.fmt);
2646 px->conf.args.ctx = ARGC_SRV;
2647 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2648 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2649 goto error;
2650 }
2651 break;
2652 case TCPCHK_SEND_HTTP:
2653 case TCPCHK_SEND_UNDEF:
2654 goto error;
2655 }
2656
2657 return chk;
2658
2659 error:
2660 free(chk);
2661 free(comment);
2662 return NULL;
2663}
2664
2665/* Parses and creates a http-check send rule. NULL is returned on error */
2666struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2667 const char *file, int line, char **errmsg)
2668{
2669 struct tcpcheck_rule *chk = NULL;
2670 struct tcpcheck_http_hdr *hdr = NULL;
2671 struct http_hdr hdrs[global.tune.max_http_hdr];
2672 char *meth = NULL, *uri = NULL, *vsn = NULL;
2673 char *body = NULL, *comment = NULL;
2674 unsigned int flags = 0;
2675 int i = 0, host_hdr = -1;
2676
2677 cur_arg++;
2678 while (*(args[cur_arg])) {
2679 if (strcmp(args[cur_arg], "meth") == 0) {
2680 if (!*(args[cur_arg+1])) {
2681 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2682 goto error;
2683 }
2684 cur_arg++;
2685 meth = args[cur_arg];
2686 }
2687 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2688 if (!*(args[cur_arg+1])) {
2689 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2690 goto error;
2691 }
2692 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2693 if (strcmp(args[cur_arg], "uri-lf") == 0)
2694 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2695 cur_arg++;
2696 uri = args[cur_arg];
2697 }
2698 else if (strcmp(args[cur_arg], "ver") == 0) {
2699 if (!*(args[cur_arg+1])) {
2700 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2701 goto error;
2702 }
2703 cur_arg++;
2704 vsn = args[cur_arg];
2705 }
2706 else if (strcmp(args[cur_arg], "hdr") == 0) {
2707 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2708 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2709 goto error;
2710 }
2711
2712 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2713 if (host_hdr >= 0) {
2714 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2715 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2716 goto error;
2717 }
2718 host_hdr = i;
2719 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002720 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002721 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2722 goto skip_hdr;
2723
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002724 hdrs[i].n = ist(args[cur_arg + 1]);
2725 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002726 i++;
2727 skip_hdr:
2728 cur_arg += 2;
2729 }
2730 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2731 if (!*(args[cur_arg+1])) {
2732 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2733 goto error;
2734 }
2735 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2736 if (strcmp(args[cur_arg], "body-lf") == 0)
2737 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2738 cur_arg++;
2739 body = args[cur_arg];
2740 }
2741 else if (strcmp(args[cur_arg], "comment") == 0) {
2742 if (!*(args[cur_arg+1])) {
2743 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2744 goto error;
2745 }
2746 cur_arg++;
2747 free(comment);
2748 comment = strdup(args[cur_arg]);
2749 if (!comment) {
2750 memprintf(errmsg, "out of memory");
2751 goto error;
2752 }
2753 }
2754 else {
2755 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2756 " but got '%s' as argument.", args[cur_arg]);
2757 goto error;
2758 }
2759 cur_arg++;
2760 }
2761
2762 hdrs[i].n = hdrs[i].v = IST_NULL;
2763
2764 chk = calloc(1, sizeof(*chk));
2765 if (!chk) {
2766 memprintf(errmsg, "out of memory");
2767 goto error;
2768 }
2769 chk->action = TCPCHK_ACT_SEND;
2770 chk->comment = comment; comment = NULL;
2771 chk->send.type = TCPCHK_SEND_HTTP;
2772 chk->send.http.flags = flags;
2773 LIST_INIT(&chk->send.http.hdrs);
2774
2775 if (meth) {
2776 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2777 chk->send.http.meth.str.area = strdup(meth);
2778 chk->send.http.meth.str.data = strlen(meth);
2779 if (!chk->send.http.meth.str.area) {
2780 memprintf(errmsg, "out of memory");
2781 goto error;
2782 }
2783 }
2784 if (uri) {
2785 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2786 LIST_INIT(&chk->send.http.uri_fmt);
2787 px->conf.args.ctx = ARGC_SRV;
2788 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2789 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2790 goto error;
2791 }
2792 }
2793 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002794 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002795 if (!isttest(chk->send.http.uri)) {
2796 memprintf(errmsg, "out of memory");
2797 goto error;
2798 }
2799 }
2800 }
2801 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002802 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002803 if (!isttest(chk->send.http.vsn)) {
2804 memprintf(errmsg, "out of memory");
2805 goto error;
2806 }
2807 }
2808 for (i = 0; istlen(hdrs[i].n); i++) {
2809 hdr = calloc(1, sizeof(*hdr));
2810 if (!hdr) {
2811 memprintf(errmsg, "out of memory");
2812 goto error;
2813 }
2814 LIST_INIT(&hdr->value);
2815 hdr->name = istdup(hdrs[i].n);
2816 if (!isttest(hdr->name)) {
2817 memprintf(errmsg, "out of memory");
2818 goto error;
2819 }
2820
2821 ist0(hdrs[i].v);
2822 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2823 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002824 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002825 hdr = NULL;
2826 }
2827
2828 if (body) {
2829 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2830 LIST_INIT(&chk->send.http.body_fmt);
2831 px->conf.args.ctx = ARGC_SRV;
2832 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2833 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2834 goto error;
2835 }
2836 }
2837 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002838 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002839 if (!isttest(chk->send.http.body)) {
2840 memprintf(errmsg, "out of memory");
2841 goto error;
2842 }
2843 }
2844 }
2845
2846 return chk;
2847
2848 error:
2849 free_tcpcheck_http_hdr(hdr);
2850 free_tcpcheck(chk, 0);
2851 free(comment);
2852 return NULL;
2853}
2854
2855/* Parses and creates a http-check comment rule. NULL is returned on error */
2856struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2857 const char *file, int line, char **errmsg)
2858{
2859 struct tcpcheck_rule *chk = NULL;
2860 char *comment = NULL;
2861
2862 if (!*(args[cur_arg+1])) {
2863 memprintf(errmsg, "expects a string as argument");
2864 goto error;
2865 }
2866 cur_arg++;
2867 comment = strdup(args[cur_arg]);
2868 if (!comment) {
2869 memprintf(errmsg, "out of memory");
2870 goto error;
2871 }
2872
2873 chk = calloc(1, sizeof(*chk));
2874 if (!chk) {
2875 memprintf(errmsg, "out of memory");
2876 goto error;
2877 }
2878 chk->action = TCPCHK_ACT_COMMENT;
2879 chk->comment = comment;
2880 return chk;
2881
2882 error:
2883 free(comment);
2884 return NULL;
2885}
2886
2887/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2888 * on error. <proto> is set to the right protocol flags (covered by the
2889 * TCPCHK_RULES_PROTO_CHK mask).
2890 */
2891struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2892 struct list *rules, unsigned int proto,
2893 const char *file, int line, char **errmsg)
2894{
2895 struct tcpcheck_rule *prev_check, *chk = NULL;
2896 struct sample_expr *status_expr = NULL;
2897 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2898 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2899 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2900 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2901 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2902 unsigned int flags = 0;
2903 long min_recv = -1;
2904 int inverse = 0;
2905
2906 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2907 if (!*(args[cur_arg+1])) {
2908 memprintf(errmsg, "expects at least a matching pattern as arguments");
2909 goto error;
2910 }
2911
2912 cur_arg++;
2913 while (*(args[cur_arg])) {
2914 int in_pattern = 0;
2915
2916 rescan:
2917 if (strcmp(args[cur_arg], "min-recv") == 0) {
2918 if (in_pattern) {
2919 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2920 goto error;
2921 }
2922 if (!*(args[cur_arg+1])) {
2923 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2924 goto error;
2925 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002926 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002927 cur_arg++;
2928 min_recv = atol(args[cur_arg]);
2929 if (min_recv < -1 || min_recv > INT_MAX) {
2930 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2931 goto error;
2932 }
2933 }
2934 else if (*(args[cur_arg]) == '!') {
2935 in_pattern = 1;
2936 while (*(args[cur_arg]) == '!') {
2937 inverse = !inverse;
2938 args[cur_arg]++;
2939 }
2940 if (!*(args[cur_arg]))
2941 cur_arg++;
2942 goto rescan;
2943 }
2944 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2945 if (type != TCPCHK_EXPECT_UNDEF) {
2946 memprintf(errmsg, "only on pattern expected");
2947 goto error;
2948 }
2949 if (proto != TCPCHK_RULES_HTTP_CHK)
2950 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2951 else
2952 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2953
2954 if (!*(args[cur_arg+1])) {
2955 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2956 goto error;
2957 }
2958 cur_arg++;
2959 pattern = args[cur_arg];
2960 }
2961 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2962 if (proto == TCPCHK_RULES_HTTP_CHK)
2963 goto bad_http_kw;
2964 if (type != TCPCHK_EXPECT_UNDEF) {
2965 memprintf(errmsg, "only on pattern expected");
2966 goto error;
2967 }
2968 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2969
2970 if (!*(args[cur_arg+1])) {
2971 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2972 goto error;
2973 }
2974 cur_arg++;
2975 pattern = args[cur_arg];
2976 }
2977 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2978 if (type != TCPCHK_EXPECT_UNDEF) {
2979 memprintf(errmsg, "only on pattern expected");
2980 goto error;
2981 }
2982 if (proto != TCPCHK_RULES_HTTP_CHK)
2983 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2984 else {
2985 if (*(args[cur_arg]) != 's')
2986 goto bad_http_kw;
2987 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2988 }
2989
2990 if (!*(args[cur_arg+1])) {
2991 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2992 goto error;
2993 }
2994 cur_arg++;
2995 pattern = args[cur_arg];
2996 }
2997 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
2998 if (proto != TCPCHK_RULES_HTTP_CHK)
2999 goto bad_tcp_kw;
3000 if (type != TCPCHK_EXPECT_UNDEF) {
3001 memprintf(errmsg, "only on pattern expected");
3002 goto error;
3003 }
3004 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3005
3006 if (!*(args[cur_arg+1])) {
3007 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3008 goto error;
3009 }
3010 cur_arg++;
3011 pattern = args[cur_arg];
3012 }
3013 else if (strcmp(args[cur_arg], "custom") == 0) {
3014 if (in_pattern) {
3015 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3016 goto error;
3017 }
3018 if (type != TCPCHK_EXPECT_UNDEF) {
3019 memprintf(errmsg, "only on pattern expected");
3020 goto error;
3021 }
3022 type = TCPCHK_EXPECT_CUSTOM;
3023 }
3024 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3025 int orig_arg = cur_arg;
3026
3027 if (proto != TCPCHK_RULES_HTTP_CHK)
3028 goto bad_tcp_kw;
3029 if (type != TCPCHK_EXPECT_UNDEF) {
3030 memprintf(errmsg, "only on pattern expected");
3031 goto error;
3032 }
3033 type = TCPCHK_EXPECT_HTTP_HEADER;
3034
3035 if (strcmp(args[cur_arg], "fhdr") == 0)
3036 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3037
3038 /* Parse the name pattern, mandatory */
3039 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3040 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3041 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3042 args[orig_arg]);
3043 goto error;
3044 }
3045
3046 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3047 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3048
3049 cur_arg += 2;
3050 if (strcmp(args[cur_arg], "-m") == 0) {
3051 if (!*(args[cur_arg+1])) {
3052 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3053 args[orig_arg], args[cur_arg]);
3054 goto error;
3055 }
3056 if (strcmp(args[cur_arg+1], "str") == 0)
3057 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3058 else if (strcmp(args[cur_arg+1], "beg") == 0)
3059 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3060 else if (strcmp(args[cur_arg+1], "end") == 0)
3061 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3062 else if (strcmp(args[cur_arg+1], "sub") == 0)
3063 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3064 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3065 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3066 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3067 args[orig_arg]);
3068 goto error;
3069 }
3070 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3071 }
3072 else {
3073 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3074 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3075 goto error;
3076 }
3077 cur_arg += 2;
3078 }
3079 else
3080 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3081 npat = args[cur_arg];
3082
3083 if (!*(args[cur_arg+1]) ||
3084 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3085 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3086 goto next;
3087 }
3088 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3089 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3090
3091 /* Parse the value pattern, optional */
3092 if (strcmp(args[cur_arg+2], "-m") == 0) {
3093 cur_arg += 2;
3094 if (!*(args[cur_arg+1])) {
3095 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3096 args[orig_arg], args[cur_arg]);
3097 goto error;
3098 }
3099 if (strcmp(args[cur_arg+1], "str") == 0)
3100 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3101 else if (strcmp(args[cur_arg+1], "beg") == 0)
3102 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3103 else if (strcmp(args[cur_arg+1], "end") == 0)
3104 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3105 else if (strcmp(args[cur_arg+1], "sub") == 0)
3106 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3107 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3108 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3109 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3110 args[orig_arg]);
3111 goto error;
3112 }
3113 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3114 }
3115 else {
3116 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3117 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3118 goto error;
3119 }
3120 }
3121 else
3122 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3123
3124 if (!*(args[cur_arg+2])) {
3125 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3126 goto error;
3127 }
3128 vpat = args[cur_arg+2];
3129 cur_arg += 2;
3130 }
3131 else if (strcmp(args[cur_arg], "comment") == 0) {
3132 if (in_pattern) {
3133 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3134 goto error;
3135 }
3136 if (!*(args[cur_arg+1])) {
3137 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3138 goto error;
3139 }
3140 cur_arg++;
3141 free(comment);
3142 comment = strdup(args[cur_arg]);
3143 if (!comment) {
3144 memprintf(errmsg, "out of memory");
3145 goto error;
3146 }
3147 }
3148 else if (strcmp(args[cur_arg], "on-success") == 0) {
3149 if (in_pattern) {
3150 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3151 goto error;
3152 }
3153 if (!*(args[cur_arg+1])) {
3154 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3155 goto error;
3156 }
3157 cur_arg++;
3158 on_success_msg = args[cur_arg];
3159 }
3160 else if (strcmp(args[cur_arg], "on-error") == 0) {
3161 if (in_pattern) {
3162 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3163 goto error;
3164 }
3165 if (!*(args[cur_arg+1])) {
3166 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3167 goto error;
3168 }
3169 cur_arg++;
3170 on_error_msg = args[cur_arg];
3171 }
3172 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3173 if (in_pattern) {
3174 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3175 goto error;
3176 }
3177 if (!*(args[cur_arg+1])) {
3178 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3179 goto error;
3180 }
3181 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3182 ok_st = HCHK_STATUS_L7OKD;
3183 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3184 ok_st = HCHK_STATUS_L7OKCD;
3185 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3186 ok_st = HCHK_STATUS_L6OK;
3187 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3188 ok_st = HCHK_STATUS_L4OK;
3189 else {
3190 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3191 args[cur_arg], args[cur_arg+1]);
3192 goto error;
3193 }
3194 cur_arg++;
3195 }
3196 else if (strcmp(args[cur_arg], "error-status") == 0) {
3197 if (in_pattern) {
3198 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3199 goto error;
3200 }
3201 if (!*(args[cur_arg+1])) {
3202 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3203 goto error;
3204 }
3205 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3206 err_st = HCHK_STATUS_L7RSP;
3207 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3208 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003209 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3210 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003211 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3212 err_st = HCHK_STATUS_L6RSP;
3213 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3214 err_st = HCHK_STATUS_L4CON;
3215 else {
3216 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3217 args[cur_arg], args[cur_arg+1]);
3218 goto error;
3219 }
3220 cur_arg++;
3221 }
3222 else if (strcmp(args[cur_arg], "status-code") == 0) {
3223 int idx = 0;
3224
3225 if (in_pattern) {
3226 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3227 goto error;
3228 }
3229 if (!*(args[cur_arg+1])) {
3230 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3231 goto error;
3232 }
3233
3234 cur_arg++;
3235 release_sample_expr(status_expr);
3236 px->conf.args.ctx = ARGC_SRV;
3237 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003238 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003239 if (!status_expr) {
3240 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3241 goto error;
3242 }
3243 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3244 memprintf(errmsg, "error detected while parsing status-code expression : "
3245 " fetch method '%s' extracts information from '%s', "
3246 "none of which is available here.\n",
3247 args[cur_arg], sample_src_names(status_expr->fetch->use));
3248 goto error;
3249 }
3250 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3251 }
3252 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3253 if (in_pattern) {
3254 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3255 goto error;
3256 }
3257 if (!*(args[cur_arg+1])) {
3258 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3259 goto error;
3260 }
3261 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3262 tout_st = HCHK_STATUS_L7TOUT;
3263 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3264 tout_st = HCHK_STATUS_L6TOUT;
3265 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3266 tout_st = HCHK_STATUS_L4TOUT;
3267 else {
3268 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3269 args[cur_arg], args[cur_arg+1]);
3270 goto error;
3271 }
3272 cur_arg++;
3273 }
3274 else {
3275 if (proto == TCPCHK_RULES_HTTP_CHK) {
3276 bad_http_kw:
3277 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3278 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3279 }
3280 else {
3281 bad_tcp_kw:
3282 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3283 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3284 }
3285 goto error;
3286 }
3287 next:
3288 cur_arg++;
3289 }
3290
3291 chk = calloc(1, sizeof(*chk));
3292 if (!chk) {
3293 memprintf(errmsg, "out of memory");
3294 goto error;
3295 }
3296 chk->action = TCPCHK_ACT_EXPECT;
3297 LIST_INIT(&chk->expect.onerror_fmt);
3298 LIST_INIT(&chk->expect.onsuccess_fmt);
3299 chk->comment = comment; comment = NULL;
3300 chk->expect.type = type;
3301 chk->expect.min_recv = min_recv;
3302 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3303 chk->expect.ok_status = ok_st;
3304 chk->expect.err_status = err_st;
3305 chk->expect.tout_status = tout_st;
3306 chk->expect.status_expr = status_expr; status_expr = NULL;
3307
3308 if (on_success_msg) {
3309 px->conf.args.ctx = ARGC_SRV;
3310 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3311 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3312 goto error;
3313 }
3314 }
3315 if (on_error_msg) {
3316 px->conf.args.ctx = ARGC_SRV;
3317 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3318 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3319 goto error;
3320 }
3321 }
3322
3323 switch (chk->expect.type) {
3324 case TCPCHK_EXPECT_HTTP_STATUS: {
3325 const char *p = pattern;
3326 unsigned int c1,c2;
3327
3328 chk->expect.codes.codes = NULL;
3329 chk->expect.codes.num = 0;
3330 while (1) {
3331 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3332 if (*p == '-') {
3333 p++;
3334 c2 = read_uint(&p, pattern + strlen(pattern));
3335 }
3336 if (c1 > c2) {
3337 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3338 goto error;
3339 }
3340
3341 chk->expect.codes.num++;
3342 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3343 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3344 if (!chk->expect.codes.codes) {
3345 memprintf(errmsg, "out of memory");
3346 goto error;
3347 }
3348 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3349 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3350
3351 if (*p == '\0')
3352 break;
3353 if (*p != ',') {
3354 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3355 goto error;
3356 }
3357 p++;
3358 }
3359 break;
3360 }
3361 case TCPCHK_EXPECT_STRING:
3362 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003363 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003364 if (!isttest(chk->expect.data)) {
3365 memprintf(errmsg, "out of memory");
3366 goto error;
3367 }
3368 break;
3369 case TCPCHK_EXPECT_BINARY: {
3370 int len = chk->expect.data.len;
3371
3372 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3373 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3374 goto error;
3375 }
3376 chk->expect.data.len = len;
3377 break;
3378 }
3379 case TCPCHK_EXPECT_STRING_REGEX:
3380 case TCPCHK_EXPECT_BINARY_REGEX:
3381 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3382 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3383 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3384 if (!chk->expect.regex)
3385 goto error;
3386 break;
3387
3388 case TCPCHK_EXPECT_STRING_LF:
3389 case TCPCHK_EXPECT_BINARY_LF:
3390 case TCPCHK_EXPECT_HTTP_BODY_LF:
3391 LIST_INIT(&chk->expect.fmt);
3392 px->conf.args.ctx = ARGC_SRV;
3393 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3394 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3395 goto error;
3396 }
3397 break;
3398
3399 case TCPCHK_EXPECT_HTTP_HEADER:
3400 if (!npat) {
3401 memprintf(errmsg, "unexpected error, undefined header name pattern");
3402 goto error;
3403 }
3404 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3405 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3406 if (!chk->expect.hdr.name_re)
3407 goto error;
3408 }
3409 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3410 px->conf.args.ctx = ARGC_SRV;
3411 LIST_INIT(&chk->expect.hdr.name_fmt);
3412 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3413 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3414 goto error;
3415 }
3416 }
3417 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003418 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003419 if (!isttest(chk->expect.hdr.name)) {
3420 memprintf(errmsg, "out of memory");
3421 goto error;
3422 }
3423 }
3424
3425 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3426 chk->expect.hdr.value = IST_NULL;
3427 break;
3428 }
3429
3430 if (!vpat) {
3431 memprintf(errmsg, "unexpected error, undefined header value pattern");
3432 goto error;
3433 }
3434 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3435 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3436 if (!chk->expect.hdr.value_re)
3437 goto error;
3438 }
3439 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3440 px->conf.args.ctx = ARGC_SRV;
3441 LIST_INIT(&chk->expect.hdr.value_fmt);
3442 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3443 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3444 goto error;
3445 }
3446 }
3447 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003448 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003449 if (!isttest(chk->expect.hdr.value)) {
3450 memprintf(errmsg, "out of memory");
3451 goto error;
3452 }
3453 }
3454
3455 break;
3456 case TCPCHK_EXPECT_CUSTOM:
3457 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3458 break;
3459 case TCPCHK_EXPECT_UNDEF:
3460 memprintf(errmsg, "pattern not found");
3461 goto error;
3462 }
3463
3464 /* All tcp-check expect points back to the first inverse expect rule in
3465 * a chain of one or more expect rule, potentially itself.
3466 */
3467 chk->expect.head = chk;
3468 list_for_each_entry_rev(prev_check, rules, list) {
3469 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3470 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3471 chk->expect.head = prev_check;
3472 continue;
3473 }
3474 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3475 break;
3476 }
3477 return chk;
3478
3479 error:
3480 free_tcpcheck(chk, 0);
3481 free(comment);
3482 release_sample_expr(status_expr);
3483 return NULL;
3484}
3485
3486/* Overwrites fields of the old http send rule with those of the new one. When
3487 * replaced, old values are freed and replaced by the new ones. New values are
3488 * not copied but transferred. At the end <new> should be empty and can be
3489 * safely released. This function never fails.
3490 */
3491void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3492{
3493 struct logformat_node *lf, *lfb;
3494 struct tcpcheck_http_hdr *hdr, *bhdr;
3495
3496
3497 if (new->send.http.meth.str.area) {
3498 free(old->send.http.meth.str.area);
3499 old->send.http.meth.meth = new->send.http.meth.meth;
3500 old->send.http.meth.str.area = new->send.http.meth.str.area;
3501 old->send.http.meth.str.data = new->send.http.meth.str.data;
3502 new->send.http.meth.str = BUF_NULL;
3503 }
3504
3505 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3506 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3507 istfree(&old->send.http.uri);
3508 else
3509 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3510 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3511 old->send.http.uri = new->send.http.uri;
3512 new->send.http.uri = IST_NULL;
3513 }
3514 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3515 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3516 istfree(&old->send.http.uri);
3517 else
3518 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3519 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3520 LIST_INIT(&old->send.http.uri_fmt);
3521 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003522 LIST_DELETE(&lf->list);
3523 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003524 }
3525 }
3526
3527 if (isttest(new->send.http.vsn)) {
3528 istfree(&old->send.http.vsn);
3529 old->send.http.vsn = new->send.http.vsn;
3530 new->send.http.vsn = IST_NULL;
3531 }
3532
3533 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3534 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003535 LIST_DELETE(&hdr->list);
3536 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003537 }
3538
3539 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3540 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3541 istfree(&old->send.http.body);
3542 else
3543 free_tcpcheck_fmt(&old->send.http.body_fmt);
3544 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3545 old->send.http.body = new->send.http.body;
3546 new->send.http.body = IST_NULL;
3547 }
3548 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3549 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3550 istfree(&old->send.http.body);
3551 else
3552 free_tcpcheck_fmt(&old->send.http.body_fmt);
3553 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3554 LIST_INIT(&old->send.http.body_fmt);
3555 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003556 LIST_DELETE(&lf->list);
3557 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003558 }
3559 }
3560}
3561
3562/* Internal function used to add an http-check rule in a list during the config
3563 * parsing step. Depending on its type, and the previously inserted rules, a
3564 * specific action may be performed or an error may be reported. This functions
3565 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3566 * message.
3567 */
3568int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3569{
3570 struct tcpcheck_rule *r;
3571
3572 /* the implicit send rule coming from an "option httpchk" line must be
3573 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003574 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003575 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003576 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003577 * sure the ruleset remains valid.
3578 */
3579
3580 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3581 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3582 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3583 * following tests are performed :
3584 *
3585 * 1- If there is no such rule or if it is not a send rule, the implicit send
3586 * rule is pushed in front of the ruleset
3587 *
3588 * 2- If it is another implicit send rule, it is replaced with the new one.
3589 *
3590 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3591 * both, overwriting the old send rule (the explicit one) with info of the
3592 * new send rule (the implicit one).
3593 */
3594 r = get_first_tcpcheck_rule(rules);
3595 if (r && r->action == TCPCHK_ACT_CONNECT)
3596 r = get_next_tcpcheck_rule(rules, r);
3597 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003598 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003599 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003600 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003601 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003602 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003603 }
3604 else {
3605 tcpcheck_overwrite_send_http_rule(r, chk);
3606 free_tcpcheck(chk, 0);
3607 }
3608 }
3609 else {
3610 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3611 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3612 * with an existing implicit send rule, if any. At the end, if there is no error,
3613 * the rule is appended to the list.
3614 */
3615
3616 r = get_last_tcpcheck_rule(rules);
3617 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3618 /* no error */;
3619 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3620 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3621 chk->index+1);
3622 return 0;
3623 }
3624 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3625 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3626 chk->index+1);
3627 return 0;
3628 }
3629 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3630 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3631 chk->index+1);
3632 return 0;
3633 }
3634
3635 if (chk->action == TCPCHK_ACT_SEND) {
3636 r = get_first_tcpcheck_rule(rules);
3637 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3638 tcpcheck_overwrite_send_http_rule(r, chk);
3639 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003640 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003641 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3642 chk = r;
3643 }
3644 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003645 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003646 }
3647 return 1;
3648}
3649
3650/* Check tcp-check health-check configuration for the proxy <px>. */
3651static int check_proxy_tcpcheck(struct proxy *px)
3652{
3653 struct tcpcheck_rule *chk, *back;
3654 char *comment = NULL, *errmsg = NULL;
3655 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003656 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003657
3658 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3659 deinit_proxy_tcpcheck(px);
3660 goto out;
3661 }
3662
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003663 ha_free(&px->check_command);
3664 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003665
3666 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003667 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003668 ret |= ERR_ALERT | ERR_FATAL;
3669 goto out;
3670 }
3671
3672 /* HTTP ruleset only : */
3673 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3674 struct tcpcheck_rule *next;
3675
3676 /* move remaining implicit send rule from "option httpchk" line to the right place.
3677 * If such rule exists, it must be the first one. In this case, the rule is moved
3678 * after the first connect rule, if any. Otherwise, nothing is done.
3679 */
3680 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3681 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3682 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3683 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003684 LIST_DELETE(&chk->list);
3685 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003686 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003687 }
3688 }
3689
3690 /* add implicit expect rule if the last one is a send. It is inherited from previous
3691 * versions where the http expect rule was optional. Now it is possible to chained
3692 * send/expect rules but the last expect may still be implicit.
3693 */
3694 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3695 if (chk && chk->action == TCPCHK_ACT_SEND) {
3696 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3697 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3698 px->conf.file, px->conf.line, &errmsg);
3699 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003700 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003701 "(%s).\n", px->id, errmsg);
3702 free(errmsg);
3703 ret |= ERR_ALERT | ERR_FATAL;
3704 goto out;
3705 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003706 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003707 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003708 }
3709 }
3710
3711 /* For all ruleset: */
3712
3713 /* If there is no connect rule preceding all send / expect rules, an
3714 * implicit one is inserted before all others.
3715 */
3716 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3717 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3718 chk = calloc(1, sizeof(*chk));
3719 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003720 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003721 "(out of memory).\n", px->id);
3722 ret |= ERR_ALERT | ERR_FATAL;
3723 goto out;
3724 }
3725 chk->action = TCPCHK_ACT_CONNECT;
3726 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003727 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003728 }
3729
3730 /* Remove all comment rules. To do so, when a such rule is found, the
3731 * comment is assigned to the following rule(s).
3732 */
3733 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003734 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3735 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003736
3737 prev_action = chk->action;
3738 switch (chk->action) {
3739 case TCPCHK_ACT_COMMENT:
3740 free(comment);
3741 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003742 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003743 free(chk);
3744 break;
3745 case TCPCHK_ACT_CONNECT:
3746 if (!chk->comment && comment)
3747 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003748 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003749 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003750 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003751 break;
3752 case TCPCHK_ACT_SEND:
3753 case TCPCHK_ACT_EXPECT:
3754 if (!chk->comment && comment)
3755 chk->comment = strdup(comment);
3756 break;
3757 }
3758 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003759 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003760
3761 out:
3762 return ret;
3763}
3764
3765void deinit_proxy_tcpcheck(struct proxy *px)
3766{
3767 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3768 px->tcpcheck_rules.flags = 0;
3769 px->tcpcheck_rules.list = NULL;
3770}
3771
3772static void deinit_tcpchecks()
3773{
3774 struct tcpcheck_ruleset *rs;
3775 struct tcpcheck_rule *r, *rb;
3776 struct ebpt_node *node, *next;
3777
3778 node = ebpt_first(&shared_tcpchecks);
3779 while (node) {
3780 next = ebpt_next(node);
3781 ebpt_delete(node);
3782 free(node->key);
3783 rs = container_of(node, typeof(*rs), node);
3784 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003785 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003786 free_tcpcheck(r, 0);
3787 }
3788 free(rs);
3789 node = next;
3790 }
3791}
3792
3793int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3794{
3795 struct tcpcheck_rule *tcpcheck, *prev_check;
3796 struct tcpcheck_expect *expect;
3797
Willy Tarreau6922e552021-03-22 21:11:45 +01003798 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003799 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003800 tcpcheck->action = TCPCHK_ACT_EXPECT;
3801
3802 expect = &tcpcheck->expect;
3803 expect->type = TCPCHK_EXPECT_STRING;
3804 LIST_INIT(&expect->onerror_fmt);
3805 LIST_INIT(&expect->onsuccess_fmt);
3806 expect->ok_status = HCHK_STATUS_L7OKD;
3807 expect->err_status = HCHK_STATUS_L7RSP;
3808 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003809 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003810 if (!isttest(expect->data)) {
3811 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3812 return 0;
3813 }
3814
3815 /* All tcp-check expect points back to the first inverse expect rule
3816 * in a chain of one or more expect rule, potentially itself.
3817 */
3818 tcpcheck->expect.head = tcpcheck;
3819 list_for_each_entry_rev(prev_check, rules->list, list) {
3820 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3821 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3822 tcpcheck->expect.head = prev_check;
3823 continue;
3824 }
3825 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3826 break;
3827 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003828 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003829 return 1;
3830}
3831
3832int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3833{
3834 struct tcpcheck_rule *tcpcheck;
3835 struct tcpcheck_send *send;
3836 const char *in;
3837 char *dst;
3838 int i;
3839
Willy Tarreau6922e552021-03-22 21:11:45 +01003840 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003841 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003842 tcpcheck->action = TCPCHK_ACT_SEND;
3843
3844 send = &tcpcheck->send;
3845 send->type = TCPCHK_SEND_STRING;
3846
3847 for (i = 0; strs[i]; i++)
3848 send->data.len += strlen(strs[i]);
3849
3850 send->data.ptr = malloc(istlen(send->data) + 1);
3851 if (!isttest(send->data)) {
3852 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3853 return 0;
3854 }
3855
3856 dst = istptr(send->data);
3857 for (i = 0; strs[i]; i++)
3858 for (in = strs[i]; (*dst = *in++); dst++);
3859 *dst = 0;
3860
Willy Tarreau2b718102021-04-21 07:32:39 +02003861 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003862 return 1;
3863}
3864
3865/* Parses the "tcp-check" proxy keyword */
3866static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003867 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003868 char **errmsg)
3869{
3870 struct tcpcheck_ruleset *rs = NULL;
3871 struct tcpcheck_rule *chk = NULL;
3872 int index, cur_arg, ret = 0;
3873
3874 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3875 ret = 1;
3876
3877 /* Deduce the ruleset name from the proxy info */
3878 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3879 ((curpx == defpx) ? "defaults" : curpx->id),
3880 curpx->conf.file, curpx->conf.line);
3881
3882 rs = find_tcpcheck_ruleset(b_orig(&trash));
3883 if (rs == NULL) {
3884 rs = create_tcpcheck_ruleset(b_orig(&trash));
3885 if (rs == NULL) {
3886 memprintf(errmsg, "out of memory.\n");
3887 goto error;
3888 }
3889 }
3890
3891 index = 0;
3892 if (!LIST_ISEMPTY(&rs->rules)) {
3893 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3894 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003895 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003896 }
3897
3898 cur_arg = 1;
3899 if (strcmp(args[cur_arg], "connect") == 0)
3900 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3901 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3902 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3903 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3904 else if (strcmp(args[cur_arg], "expect") == 0)
3905 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3906 else if (strcmp(args[cur_arg], "comment") == 0)
3907 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3908 else {
3909 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3910
3911 if (!kw) {
3912 action_kw_tcp_check_build_list(&trash);
3913 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3914 "%s%s. but got '%s'",
3915 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3916 goto error;
3917 }
3918 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3919 }
3920
3921 if (!chk) {
3922 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3923 goto error;
3924 }
3925 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3926
3927 /* No error: add the tcp-check rule in the list */
3928 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003929 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003930
3931 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3932 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3933 /* Use this ruleset if the proxy already has tcp-check enabled */
3934 curpx->tcpcheck_rules.list = &rs->rules;
3935 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3936 }
3937 else {
3938 /* mark this ruleset as unused for now */
3939 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3940 }
3941
3942 return ret;
3943
3944 error:
3945 free_tcpcheck(chk, 0);
3946 free_tcpcheck_ruleset(rs);
3947 return -1;
3948}
3949
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003950/* Parses the "http-check" proxy keyword */
3951static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003952 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003953 char **errmsg)
3954{
3955 struct tcpcheck_ruleset *rs = NULL;
3956 struct tcpcheck_rule *chk = NULL;
3957 int index, cur_arg, ret = 0;
3958
3959 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3960 ret = 1;
3961
3962 cur_arg = 1;
3963 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3964 /* enable a graceful server shutdown on an HTTP 404 response */
3965 curpx->options |= PR_O_DISABLE404;
3966 if (too_many_args(1, args, errmsg, NULL))
3967 goto error;
3968 goto out;
3969 }
3970 else if (strcmp(args[cur_arg], "send-state") == 0) {
3971 /* enable emission of the apparent state of a server in HTTP checks */
3972 curpx->options2 |= PR_O2_CHK_SNDST;
3973 if (too_many_args(1, args, errmsg, NULL))
3974 goto error;
3975 goto out;
3976 }
3977
3978 /* Deduce the ruleset name from the proxy info */
3979 chunk_printf(&trash, "*http-check-%s_%s-%d",
3980 ((curpx == defpx) ? "defaults" : curpx->id),
3981 curpx->conf.file, curpx->conf.line);
3982
3983 rs = find_tcpcheck_ruleset(b_orig(&trash));
3984 if (rs == NULL) {
3985 rs = create_tcpcheck_ruleset(b_orig(&trash));
3986 if (rs == NULL) {
3987 memprintf(errmsg, "out of memory.\n");
3988 goto error;
3989 }
3990 }
3991
3992 index = 0;
3993 if (!LIST_ISEMPTY(&rs->rules)) {
3994 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3995 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
3996 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003997 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003998 }
3999
4000 if (strcmp(args[cur_arg], "connect") == 0)
4001 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4002 else if (strcmp(args[cur_arg], "send") == 0)
4003 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4004 else if (strcmp(args[cur_arg], "expect") == 0)
4005 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4006 file, line, errmsg);
4007 else if (strcmp(args[cur_arg], "comment") == 0)
4008 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4009 else {
4010 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4011
4012 if (!kw) {
4013 action_kw_tcp_check_build_list(&trash);
4014 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4015 " 'send', 'expect'%s%s. but got '%s'",
4016 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4017 goto error;
4018 }
4019 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4020 }
4021
4022 if (!chk) {
4023 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4024 goto error;
4025 }
4026 ret = (*errmsg != NULL); /* Handle warning */
4027
4028 chk->index = index;
4029 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4030 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4031 /* Use this ruleset if the proxy already has http-check enabled */
4032 curpx->tcpcheck_rules.list = &rs->rules;
4033 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4034 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4035 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4036 curpx->tcpcheck_rules.list = NULL;
4037 goto error;
4038 }
4039 }
4040 else {
4041 /* mark this ruleset as unused for now */
4042 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004043 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004044 }
4045
4046 out:
4047 return ret;
4048
4049 error:
4050 free_tcpcheck(chk, 0);
4051 free_tcpcheck_ruleset(rs);
4052 return -1;
4053}
4054
4055/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004056int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004057 const char *file, int line)
4058{
4059 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4060 static char *redis_res = "+PONG\r\n";
4061
4062 struct tcpcheck_ruleset *rs = NULL;
4063 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4064 struct tcpcheck_rule *chk;
4065 char *errmsg = NULL;
4066 int err_code = 0;
4067
4068 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4069 err_code |= ERR_WARN;
4070
4071 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4072 goto out;
4073
4074 curpx->options2 &= ~PR_O2_CHK_ANY;
4075 curpx->options2 |= PR_O2_TCPCHK_CHK;
4076
4077 free_tcpcheck_vars(&rules->preset_vars);
4078 rules->list = NULL;
4079 rules->flags = 0;
4080
4081 rs = find_tcpcheck_ruleset("*redis-check");
4082 if (rs)
4083 goto ruleset_found;
4084
4085 rs = create_tcpcheck_ruleset("*redis-check");
4086 if (rs == NULL) {
4087 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4088 goto error;
4089 }
4090
4091 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4092 1, curpx, &rs->rules, file, line, &errmsg);
4093 if (!chk) {
4094 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4095 goto error;
4096 }
4097 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004098 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004099
4100 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4101 "error-status", "L7STS",
4102 "on-error", "%[res.payload(0,0),cut_crlf]",
4103 "on-success", "Redis server is ok",
4104 ""},
4105 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4106 if (!chk) {
4107 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4108 goto error;
4109 }
4110 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004111 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004112
4113 ruleset_found:
4114 rules->list = &rs->rules;
4115 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4116 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4117
4118 out:
4119 free(errmsg);
4120 return err_code;
4121
4122 error:
4123 free_tcpcheck_ruleset(rs);
4124 err_code |= ERR_ALERT | ERR_FATAL;
4125 goto out;
4126}
4127
4128
4129/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004130int 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 +01004131 const char *file, int line)
4132{
4133 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4134 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4135 *
4136 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4137 */
4138 static char sslv3_client_hello[] = {
4139 "16" /* ContentType : 0x16 = Handshake */
4140 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4141 "0079" /* ContentLength : 0x79 bytes after this one */
4142 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4143 "000075" /* HandshakeLength : 0x75 bytes after this one */
4144 "0300" /* Hello Version : 0x0300 = v3 */
4145 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4146 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4147 "00" /* Session ID length : empty (no session ID) */
4148 "004E" /* Cipher Suite Length : 78 bytes after this one */
4149 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4150 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4151 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4152 "000D" "000E" "000F" "0010" /* various bit lengths, */
4153 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4154 "0015" "0016" "0017" "0018"
4155 "0019" "001A" "001B" "002F"
4156 "0030" "0031" "0032" "0033"
4157 "0034" "0035" "0036" "0037"
4158 "0038" "0039" "003A"
4159 "01" /* Compression Length : 0x01 = 1 byte for types */
4160 "00" /* Compression Type : 0x00 = NULL compression */
4161 };
4162
4163 struct tcpcheck_ruleset *rs = NULL;
4164 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4165 struct tcpcheck_rule *chk;
4166 char *errmsg = NULL;
4167 int err_code = 0;
4168
4169 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4170 err_code |= ERR_WARN;
4171
4172 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4173 goto out;
4174
4175 curpx->options2 &= ~PR_O2_CHK_ANY;
4176 curpx->options2 |= PR_O2_TCPCHK_CHK;
4177
4178 free_tcpcheck_vars(&rules->preset_vars);
4179 rules->list = NULL;
4180 rules->flags = 0;
4181
4182 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4183 if (rs)
4184 goto ruleset_found;
4185
4186 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4187 if (rs == NULL) {
4188 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4189 goto error;
4190 }
4191
4192 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4193 1, curpx, &rs->rules, file, line, &errmsg);
4194 if (!chk) {
4195 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4196 goto error;
4197 }
4198 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004199 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004200
4201 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4202 "min-recv", "5", "ok-status", "L6OK",
4203 "error-status", "L6RSP", "tout-status", "L6TOUT",
4204 ""},
4205 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4206 if (!chk) {
4207 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4208 goto error;
4209 }
4210 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004211 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004212
4213 ruleset_found:
4214 rules->list = &rs->rules;
4215 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4216 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4217
4218 out:
4219 free(errmsg);
4220 return err_code;
4221
4222 error:
4223 free_tcpcheck_ruleset(rs);
4224 err_code |= ERR_ALERT | ERR_FATAL;
4225 goto out;
4226}
4227
4228/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004229int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004230 const char *file, int line)
4231{
4232 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4233
4234 struct tcpcheck_ruleset *rs = NULL;
4235 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4236 struct tcpcheck_rule *chk;
4237 struct tcpcheck_var *var = NULL;
4238 char *cmd = NULL, *errmsg = NULL;
4239 int err_code = 0;
4240
4241 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4242 err_code |= ERR_WARN;
4243
4244 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4245 goto out;
4246
4247 curpx->options2 &= ~PR_O2_CHK_ANY;
4248 curpx->options2 |= PR_O2_TCPCHK_CHK;
4249
4250 free_tcpcheck_vars(&rules->preset_vars);
4251 rules->list = NULL;
4252 rules->flags = 0;
4253
4254 cur_arg += 2;
4255 if (*args[cur_arg] && *args[cur_arg+1] &&
4256 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4257 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4258 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4259 if (cmd)
4260 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4261 }
4262 else {
4263 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4264 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4265 cmd = strdup("HELO localhost");
4266 }
4267
4268 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4269 if (cmd == NULL || var == NULL) {
4270 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4271 goto error;
4272 }
4273 var->data.type = SMP_T_STR;
4274 var->data.u.str.area = cmd;
4275 var->data.u.str.data = strlen(cmd);
4276 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004277 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004278 cmd = NULL;
4279 var = NULL;
4280
4281 rs = find_tcpcheck_ruleset("*smtp-check");
4282 if (rs)
4283 goto ruleset_found;
4284
4285 rs = create_tcpcheck_ruleset("*smtp-check");
4286 if (rs == NULL) {
4287 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4288 goto error;
4289 }
4290
4291 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4292 1, curpx, &rs->rules, file, line, &errmsg);
4293 if (!chk) {
4294 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4295 goto error;
4296 }
4297 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004298 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004299
4300 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4301 "min-recv", "4",
4302 "error-status", "L7RSP",
4303 "on-error", "%[res.payload(0,0),cut_crlf]",
4304 ""},
4305 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4306 if (!chk) {
4307 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4308 goto error;
4309 }
4310 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004311 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004312
4313 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4314 "min-recv", "4",
4315 "error-status", "L7STS",
4316 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4317 "status-code", "res.payload(0,3)",
4318 ""},
4319 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4320 if (!chk) {
4321 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4322 goto error;
4323 }
4324 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004325 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004326
4327 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4328 1, curpx, &rs->rules, file, line, &errmsg);
4329 if (!chk) {
4330 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4331 goto error;
4332 }
4333 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004334 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004335
4336 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4337 "min-recv", "4",
4338 "error-status", "L7STS",
4339 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4340 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4341 "status-code", "res.payload(0,3)",
4342 ""},
4343 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4344 if (!chk) {
4345 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4346 goto error;
4347 }
4348 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004349 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004350
4351 ruleset_found:
4352 rules->list = &rs->rules;
4353 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4354 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4355
4356 out:
4357 free(errmsg);
4358 return err_code;
4359
4360 error:
4361 free(cmd);
4362 free(var);
4363 free_tcpcheck_vars(&rules->preset_vars);
4364 free_tcpcheck_ruleset(rs);
4365 err_code |= ERR_ALERT | ERR_FATAL;
4366 goto out;
4367}
4368
4369/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004370int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004371 const char *file, int line)
4372{
4373 static char pgsql_req[] = {
4374 "%[var(check.plen),htonl,hex]" /* The packet length*/
4375 "00030000" /* the version 3.0 */
4376 "7573657200" /* "user" key */
4377 "%[var(check.username),hex]00" /* the username */
4378 "00"
4379 };
4380
4381 struct tcpcheck_ruleset *rs = NULL;
4382 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4383 struct tcpcheck_rule *chk;
4384 struct tcpcheck_var *var = NULL;
4385 char *user = NULL, *errmsg = NULL;
4386 size_t packetlen = 0;
4387 int err_code = 0;
4388
4389 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4390 err_code |= ERR_WARN;
4391
4392 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4393 goto out;
4394
4395 curpx->options2 &= ~PR_O2_CHK_ANY;
4396 curpx->options2 |= PR_O2_TCPCHK_CHK;
4397
4398 free_tcpcheck_vars(&rules->preset_vars);
4399 rules->list = NULL;
4400 rules->flags = 0;
4401
4402 cur_arg += 2;
4403 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4404 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4405 file, line, args[0], args[1]);
4406 goto error;
4407 }
4408 if (strcmp(args[cur_arg], "user") == 0) {
4409 packetlen = 15 + strlen(args[cur_arg+1]);
4410 user = strdup(args[cur_arg+1]);
4411
4412 var = create_tcpcheck_var(ist("check.username"));
4413 if (user == NULL || var == NULL) {
4414 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4415 goto error;
4416 }
4417 var->data.type = SMP_T_STR;
4418 var->data.u.str.area = user;
4419 var->data.u.str.data = strlen(user);
4420 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004421 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004422 user = NULL;
4423 var = NULL;
4424
4425 var = create_tcpcheck_var(ist("check.plen"));
4426 if (var == NULL) {
4427 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4428 goto error;
4429 }
4430 var->data.type = SMP_T_SINT;
4431 var->data.u.sint = packetlen;
4432 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004433 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004434 var = NULL;
4435 }
4436 else {
4437 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4438 file, line, args[0], args[1]);
4439 goto error;
4440 }
4441
4442 rs = find_tcpcheck_ruleset("*pgsql-check");
4443 if (rs)
4444 goto ruleset_found;
4445
4446 rs = create_tcpcheck_ruleset("*pgsql-check");
4447 if (rs == NULL) {
4448 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4449 goto error;
4450 }
4451
4452 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4453 1, curpx, &rs->rules, file, line, &errmsg);
4454 if (!chk) {
4455 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4456 goto error;
4457 }
4458 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004459 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004460
4461 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4462 1, curpx, &rs->rules, file, line, &errmsg);
4463 if (!chk) {
4464 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4465 goto error;
4466 }
4467 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004468 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004469
4470 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4471 "min-recv", "5",
4472 "error-status", "L7RSP",
4473 "on-error", "%[res.payload(6,0)]",
4474 ""},
4475 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4476 if (!chk) {
4477 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4478 goto error;
4479 }
4480 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004481 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004482
4483 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4484 "min-recv", "9",
4485 "error-status", "L7STS",
4486 "on-success", "PostgreSQL server is ok",
4487 "on-error", "PostgreSQL unknown error",
4488 ""},
4489 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4490 if (!chk) {
4491 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4492 goto error;
4493 }
4494 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004495 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004496
4497 ruleset_found:
4498 rules->list = &rs->rules;
4499 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4500 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4501
4502 out:
4503 free(errmsg);
4504 return err_code;
4505
4506 error:
4507 free(user);
4508 free(var);
4509 free_tcpcheck_vars(&rules->preset_vars);
4510 free_tcpcheck_ruleset(rs);
4511 err_code |= ERR_ALERT | ERR_FATAL;
4512 goto out;
4513}
4514
4515
4516/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004517int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004518 const char *file, int line)
4519{
4520 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4521 * const char mysql40_client_auth_pkt[] = {
4522 * "\x0e\x00\x00" // packet length
4523 * "\x01" // packet number
4524 * "\x00\x00" // client capabilities
4525 * "\x00\x00\x01" // max packet
4526 * "haproxy\x00" // username (null terminated string)
4527 * "\x00" // filler (always 0x00)
4528 * "\x01\x00\x00" // packet length
4529 * "\x00" // packet number
4530 * "\x01" // COM_QUIT command
4531 * };
4532 */
4533 static char mysql40_rsname[] = "*mysql40-check";
4534 static char mysql40_req[] = {
4535 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4536 "0080" /* client capabilities */
4537 "000001" /* max packet */
4538 "%[var(check.username),hex]00" /* the username */
4539 "00" /* filler (always 0x00) */
4540 "010000" /* packet length*/
4541 "00" /* sequence ID */
4542 "01" /* COM_QUIT command */
4543 };
4544
4545 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4546 * const char mysql41_client_auth_pkt[] = {
4547 * "\x0e\x00\x00\" // packet length
4548 * "\x01" // packet number
4549 * "\x00\x00\x00\x00" // client capabilities
4550 * "\x00\x00\x00\x01" // max packet
4551 * "\x21" // character set (UTF-8)
4552 * char[23] // All zeroes
4553 * "haproxy\x00" // username (null terminated string)
4554 * "\x00" // filler (always 0x00)
4555 * "\x01\x00\x00" // packet length
4556 * "\x00" // packet number
4557 * "\x01" // COM_QUIT command
4558 * };
4559 */
4560 static char mysql41_rsname[] = "*mysql41-check";
4561 static char mysql41_req[] = {
4562 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4563 "00820000" /* client capabilities */
4564 "00800001" /* max packet */
4565 "21" /* character set (UTF-8) */
4566 "000000000000000000000000" /* 23 bytes, al zeroes */
4567 "0000000000000000000000"
4568 "%[var(check.username),hex]00" /* the username */
4569 "00" /* filler (always 0x00) */
4570 "010000" /* packet length*/
4571 "00" /* sequence ID */
4572 "01" /* COM_QUIT command */
4573 };
4574
4575 struct tcpcheck_ruleset *rs = NULL;
4576 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4577 struct tcpcheck_rule *chk;
4578 struct tcpcheck_var *var = NULL;
4579 char *mysql_rsname = "*mysql-check";
4580 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4581 int index = 0, err_code = 0;
4582
4583 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4584 err_code |= ERR_WARN;
4585
4586 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4587 goto out;
4588
4589 curpx->options2 &= ~PR_O2_CHK_ANY;
4590 curpx->options2 |= PR_O2_TCPCHK_CHK;
4591
4592 free_tcpcheck_vars(&rules->preset_vars);
4593 rules->list = NULL;
4594 rules->flags = 0;
4595
4596 cur_arg += 2;
4597 if (*args[cur_arg]) {
4598 int packetlen, userlen;
4599
4600 if (strcmp(args[cur_arg], "user") != 0) {
4601 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4602 file, line, args[0], args[1], args[cur_arg]);
4603 goto error;
4604 }
4605
4606 if (*(args[cur_arg+1]) == 0) {
4607 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4608 file, line, args[0], args[1], args[cur_arg]);
4609 goto error;
4610 }
4611
4612 hdr = calloc(4, sizeof(*hdr));
4613 user = strdup(args[cur_arg+1]);
4614 userlen = strlen(args[cur_arg+1]);
4615
4616 if (hdr == NULL || user == NULL) {
4617 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4618 goto error;
4619 }
4620
4621 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4622 packetlen = userlen + 7 + 27;
4623 mysql_req = mysql41_req;
4624 mysql_rsname = mysql41_rsname;
4625 }
4626 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4627 packetlen = userlen + 7;
4628 mysql_req = mysql40_req;
4629 mysql_rsname = mysql40_rsname;
4630 }
4631 else {
4632 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4633 file, line, args[cur_arg], args[cur_arg+2]);
4634 goto error;
4635 }
4636
4637 hdr[0] = (unsigned char)(packetlen & 0xff);
4638 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4639 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4640 hdr[3] = 1;
4641
4642 var = create_tcpcheck_var(ist("check.header"));
4643 if (var == NULL) {
4644 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4645 goto error;
4646 }
4647 var->data.type = SMP_T_STR;
4648 var->data.u.str.area = hdr;
4649 var->data.u.str.data = 4;
4650 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004651 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004652 hdr = NULL;
4653 var = NULL;
4654
4655 var = create_tcpcheck_var(ist("check.username"));
4656 if (var == NULL) {
4657 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4658 goto error;
4659 }
4660 var->data.type = SMP_T_STR;
4661 var->data.u.str.area = user;
4662 var->data.u.str.data = strlen(user);
4663 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004664 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004665 user = NULL;
4666 var = NULL;
4667 }
4668
4669 rs = find_tcpcheck_ruleset(mysql_rsname);
4670 if (rs)
4671 goto ruleset_found;
4672
4673 rs = create_tcpcheck_ruleset(mysql_rsname);
4674 if (rs == NULL) {
4675 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4676 goto error;
4677 }
4678
4679 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4680 1, curpx, &rs->rules, file, line, &errmsg);
4681 if (!chk) {
4682 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4683 goto error;
4684 }
4685 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004686 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004687
4688 if (mysql_req) {
4689 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4690 1, curpx, &rs->rules, file, line, &errmsg);
4691 if (!chk) {
4692 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4693 goto error;
4694 }
4695 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004696 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004697 }
4698
4699 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4700 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4701 if (!chk) {
4702 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4703 goto error;
4704 }
4705 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4706 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004707 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004708
4709 if (mysql_req) {
4710 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4711 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4712 if (!chk) {
4713 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4714 goto error;
4715 }
4716 chk->expect.custom = tcpcheck_mysql_expect_ok;
4717 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004718 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004719 }
4720
4721 ruleset_found:
4722 rules->list = &rs->rules;
4723 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4724 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4725
4726 out:
4727 free(errmsg);
4728 return err_code;
4729
4730 error:
4731 free(hdr);
4732 free(user);
4733 free(var);
4734 free_tcpcheck_vars(&rules->preset_vars);
4735 free_tcpcheck_ruleset(rs);
4736 err_code |= ERR_ALERT | ERR_FATAL;
4737 goto out;
4738}
4739
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004740int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004741 const char *file, int line)
4742{
4743 static char *ldap_req = "300C020101600702010304008000";
4744
4745 struct tcpcheck_ruleset *rs = NULL;
4746 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4747 struct tcpcheck_rule *chk;
4748 char *errmsg = NULL;
4749 int err_code = 0;
4750
4751 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4752 err_code |= ERR_WARN;
4753
4754 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4755 goto out;
4756
4757 curpx->options2 &= ~PR_O2_CHK_ANY;
4758 curpx->options2 |= PR_O2_TCPCHK_CHK;
4759
4760 free_tcpcheck_vars(&rules->preset_vars);
4761 rules->list = NULL;
4762 rules->flags = 0;
4763
4764 rs = find_tcpcheck_ruleset("*ldap-check");
4765 if (rs)
4766 goto ruleset_found;
4767
4768 rs = create_tcpcheck_ruleset("*ldap-check");
4769 if (rs == NULL) {
4770 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4771 goto error;
4772 }
4773
4774 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4775 1, curpx, &rs->rules, file, line, &errmsg);
4776 if (!chk) {
4777 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4778 goto error;
4779 }
4780 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004781 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004782
4783 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4784 "min-recv", "14",
4785 "on-error", "Not LDAPv3 protocol",
4786 ""},
4787 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4788 if (!chk) {
4789 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4790 goto error;
4791 }
4792 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004793 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004794
4795 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4796 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4797 if (!chk) {
4798 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4799 goto error;
4800 }
4801 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4802 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004803 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004804
4805 ruleset_found:
4806 rules->list = &rs->rules;
4807 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4808 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4809
4810 out:
4811 free(errmsg);
4812 return err_code;
4813
4814 error:
4815 free_tcpcheck_ruleset(rs);
4816 err_code |= ERR_ALERT | ERR_FATAL;
4817 goto out;
4818}
4819
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004820int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004821 const char *file, int line)
4822{
4823 struct tcpcheck_ruleset *rs = NULL;
4824 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4825 struct tcpcheck_rule *chk;
4826 char *spop_req = NULL;
4827 char *errmsg = NULL;
4828 int spop_len = 0, err_code = 0;
4829
4830 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4831 err_code |= ERR_WARN;
4832
4833 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4834 goto out;
4835
4836 curpx->options2 &= ~PR_O2_CHK_ANY;
4837 curpx->options2 |= PR_O2_TCPCHK_CHK;
4838
4839 free_tcpcheck_vars(&rules->preset_vars);
4840 rules->list = NULL;
4841 rules->flags = 0;
4842
4843
4844 rs = find_tcpcheck_ruleset("*spop-check");
4845 if (rs)
4846 goto ruleset_found;
4847
4848 rs = create_tcpcheck_ruleset("*spop-check");
4849 if (rs == NULL) {
4850 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4851 goto error;
4852 }
4853
4854 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4855 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4856 goto error;
4857 }
4858 chunk_reset(&trash);
4859 dump_binary(&trash, spop_req, spop_len);
4860 trash.area[trash.data] = '\0';
4861
4862 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4863 1, curpx, &rs->rules, file, line, &errmsg);
4864 if (!chk) {
4865 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4866 goto error;
4867 }
4868 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004869 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004870
4871 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4872 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4873 if (!chk) {
4874 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4875 goto error;
4876 }
4877 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4878 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004879 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004880
4881 ruleset_found:
4882 rules->list = &rs->rules;
4883 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4884 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4885
4886 out:
4887 free(spop_req);
4888 free(errmsg);
4889 return err_code;
4890
4891 error:
4892 free_tcpcheck_ruleset(rs);
4893 err_code |= ERR_ALERT | ERR_FATAL;
4894 goto out;
4895}
4896
4897
4898static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4899{
4900 struct tcpcheck_rule *chk = NULL;
4901 struct tcpcheck_http_hdr *hdr = NULL;
4902 char *meth = NULL, *uri = NULL, *vsn = NULL;
4903 char *hdrs, *body;
4904
4905 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4906 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4907 if (hdrs == body)
4908 hdrs = NULL;
4909 if (hdrs) {
4910 *hdrs = '\0';
4911 hdrs +=2;
4912 }
4913 if (body) {
4914 *body = '\0';
4915 body += 4;
4916 }
4917 if (hdrs || body) {
4918 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4919 " Please, consider to use 'http-check send' directive instead.");
4920 }
4921
4922 chk = calloc(1, sizeof(*chk));
4923 if (!chk) {
4924 memprintf(errmsg, "out of memory");
4925 goto error;
4926 }
4927 chk->action = TCPCHK_ACT_SEND;
4928 chk->send.type = TCPCHK_SEND_HTTP;
4929 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4930 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4931 LIST_INIT(&chk->send.http.hdrs);
4932
4933 /* Copy the method, uri and version */
4934 if (*args[cur_arg]) {
4935 if (!*args[cur_arg+1])
4936 uri = args[cur_arg];
4937 else
4938 meth = args[cur_arg];
4939 }
4940 if (*args[cur_arg+1])
4941 uri = args[cur_arg+1];
4942 if (*args[cur_arg+2])
4943 vsn = args[cur_arg+2];
4944
4945 if (meth) {
4946 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4947 chk->send.http.meth.str.area = strdup(meth);
4948 chk->send.http.meth.str.data = strlen(meth);
4949 if (!chk->send.http.meth.str.area) {
4950 memprintf(errmsg, "out of memory");
4951 goto error;
4952 }
4953 }
4954 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004955 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004956 if (!isttest(chk->send.http.uri)) {
4957 memprintf(errmsg, "out of memory");
4958 goto error;
4959 }
4960 }
4961 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004962 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004963 if (!isttest(chk->send.http.vsn)) {
4964 memprintf(errmsg, "out of memory");
4965 goto error;
4966 }
4967 }
4968
4969 /* Copy the header */
4970 if (hdrs) {
4971 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4972 struct h1m h1m;
4973 int i, ret;
4974
4975 /* Build and parse the request */
4976 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4977
4978 h1m.flags = H1_MF_HDRS_ONLY;
4979 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4980 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4981 &h1m, NULL);
4982 if (ret <= 0) {
4983 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4984 goto error;
4985 }
4986
4987 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4988 hdr = calloc(1, sizeof(*hdr));
4989 if (!hdr) {
4990 memprintf(errmsg, "out of memory");
4991 goto error;
4992 }
4993 LIST_INIT(&hdr->value);
4994 hdr->name = istdup(tmp_hdrs[i].n);
Tim Duesterhus77508502022-03-15 13:11:06 +01004995 if (!isttest(hdr->name)) {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004996 memprintf(errmsg, "out of memory");
4997 goto error;
4998 }
4999
5000 ist0(tmp_hdrs[i].v);
5001 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5002 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005003 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005004 }
5005 }
5006
5007 /* Copy the body */
5008 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005009 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005010 if (!isttest(chk->send.http.body)) {
5011 memprintf(errmsg, "out of memory");
5012 goto error;
5013 }
5014 }
5015
5016 return chk;
5017
5018 error:
5019 free_tcpcheck_http_hdr(hdr);
5020 free_tcpcheck(chk, 0);
5021 return NULL;
5022}
5023
5024/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005025int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005026 const char *file, int line)
5027{
5028 struct tcpcheck_ruleset *rs = NULL;
5029 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5030 struct tcpcheck_rule *chk;
5031 char *errmsg = NULL;
5032 int err_code = 0;
5033
5034 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5035 err_code |= ERR_WARN;
5036
5037 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5038 goto out;
5039
5040 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5041 if (!chk) {
5042 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5043 goto error;
5044 }
5045 if (errmsg) {
5046 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5047 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005048 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005049 }
5050
5051 no_request:
5052 curpx->options2 &= ~PR_O2_CHK_ANY;
5053 curpx->options2 |= PR_O2_TCPCHK_CHK;
5054
5055 free_tcpcheck_vars(&rules->preset_vars);
5056 rules->list = NULL;
5057 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5058
5059 /* Deduce the ruleset name from the proxy info */
5060 chunk_printf(&trash, "*http-check-%s_%s-%d",
5061 ((curpx == defpx) ? "defaults" : curpx->id),
5062 curpx->conf.file, curpx->conf.line);
5063
5064 rs = find_tcpcheck_ruleset(b_orig(&trash));
5065 if (rs == NULL) {
5066 rs = create_tcpcheck_ruleset(b_orig(&trash));
5067 if (rs == NULL) {
5068 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5069 goto error;
5070 }
5071 }
5072
5073 rules->list = &rs->rules;
5074 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5075 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5076 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5077 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5078 rules->list = NULL;
5079 goto error;
5080 }
5081
5082 out:
5083 free(errmsg);
5084 return err_code;
5085
5086 error:
5087 free_tcpcheck_ruleset(rs);
5088 free_tcpcheck(chk, 0);
5089 err_code |= ERR_ALERT | ERR_FATAL;
5090 goto out;
5091}
5092
5093/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005094int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005095 const char *file, int line)
5096{
5097 struct tcpcheck_ruleset *rs = NULL;
5098 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5099 int err_code = 0;
5100
5101 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5102 err_code |= ERR_WARN;
5103
5104 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5105 goto out;
5106
5107 curpx->options2 &= ~PR_O2_CHK_ANY;
5108 curpx->options2 |= PR_O2_TCPCHK_CHK;
5109
5110 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5111 /* If a tcp-check rulesset is already set, do nothing */
5112 if (rules->list)
5113 goto out;
5114
5115 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5116 * get it.
5117 */
5118 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5119 goto curpx_ruleset;
5120
5121 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5122 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5123 rs = find_tcpcheck_ruleset(b_orig(&trash));
5124 if (rs)
5125 goto ruleset_found;
5126 }
5127
5128 curpx_ruleset:
5129 /* Deduce the ruleset name from the proxy info */
5130 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5131 ((curpx == defpx) ? "defaults" : curpx->id),
5132 curpx->conf.file, curpx->conf.line);
5133
5134 rs = find_tcpcheck_ruleset(b_orig(&trash));
5135 if (rs == NULL) {
5136 rs = create_tcpcheck_ruleset(b_orig(&trash));
5137 if (rs == NULL) {
5138 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5139 goto error;
5140 }
5141 }
5142
5143 ruleset_found:
5144 free_tcpcheck_vars(&rules->preset_vars);
5145 rules->list = &rs->rules;
5146 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5147 rules->flags |= TCPCHK_RULES_TCP_CHK;
5148
5149 out:
5150 return err_code;
5151
5152 error:
5153 err_code |= ERR_ALERT | ERR_FATAL;
5154 goto out;
5155}
5156
Willy Tarreau51cd5952020-06-05 12:25:38 +02005157static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005158 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005159 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5160 { 0, NULL, NULL },
5161}};
5162
5163REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5164REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5165REGISTER_POST_DEINIT(deinit_tcpchecks);
5166INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);