blob: 930735b70d851f91a6b52841426dd3dea4865511 [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 Fauleta9e8b392022-03-23 11:01:09 +01001104 cs_attach_mux(check->cs, NULL, conn);
1105 conn->ctx = check->cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001106 tasklet_set_tid(check->wait_list.tasklet, tid);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001107 conn_set_owner(conn, check->sess, NULL);
1108
1109 /* Maybe there were an older connection we were waiting on */
1110 check->wait_list.events = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001111
1112 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001113 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001114 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001115 status = SF_ERR_RESOURCE;
1116 goto fail_check;
1117 }
1118
1119 /* connect to the connect rule addr if specified, otherwise the check
1120 * addr if specified on the server. otherwise, use the server addr (it
1121 * MUST exist at this step).
1122 */
1123 *conn->dst = (is_addr(&connect->addr)
1124 ? connect->addr
1125 : (is_addr(&check->addr) ? check->addr : s->addr));
Willy Tarreau14e7f292021-10-27 17:41:07 +02001126 proto = protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001127
1128 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001129 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001130 port = connect->port;
1131 if (!port && connect->port_expr) {
1132 struct sample *smp;
1133
1134 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1135 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1136 connect->port_expr, SMP_T_SINT);
1137 if (smp)
1138 port = smp->data.u.sint;
1139 }
1140 if (!port && is_inet_addr(&connect->addr))
1141 port = get_host_port(&connect->addr);
1142 if (!port && check->port)
1143 port = check->port;
1144 if (!port && is_inet_addr(&check->addr))
1145 port = get_host_port(&check->addr);
1146 if (!port) {
1147 /* The server MUST exist here */
1148 port = s->svc_port;
1149 }
1150 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001151 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001152
1153 xprt = ((connect->options & TCPCHK_OPT_SSL)
1154 ? xprt_get(XPRT_SSL)
1155 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1156
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001157 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001158 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001159 status = SF_ERR_RESOURCE;
1160 goto fail_check;
1161 }
1162
Christopher Fauletf7177272020-10-02 13:41:55 +02001163 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1164 conn->send_proxy_ofs = 1;
1165 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001166 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001167 }
1168 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1169 conn->send_proxy_ofs = 1;
1170 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001171 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001172 }
1173
1174 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1175 conn->send_proxy_ofs = 1;
1176 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001177 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001178 }
1179 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1180 conn->send_proxy_ofs = 1;
1181 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001182 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001183 }
1184
Willy Tarreau51cd5952020-06-05 12:25:38 +02001185 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001186 if (proto && proto->connect) {
1187 int flags = 0;
1188
1189 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1190 flags |= CONNECT_HAS_DATA;
1191 if (!next || next->action != TCPCHK_ACT_EXPECT)
1192 flags |= CONNECT_DELACK_ALWAYS;
1193 status = proto->connect(conn, flags);
1194 }
1195
1196 if (status != SF_ERR_NONE)
1197 goto fail_check;
1198
Christopher Faulet21ddc742020-07-01 15:26:14 +02001199 conn_set_private(conn);
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001200 conn->ctx = check->cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001201
Willy Tarreau51cd5952020-06-05 12:25:38 +02001202#ifdef USE_OPENSSL
1203 if (connect->sni)
1204 ssl_sock_set_servername(conn, connect->sni);
1205 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1206 ssl_sock_set_servername(conn, s->check.sni);
1207
1208 if (connect->alpn)
1209 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1210 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1211 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1212#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001213
Willy Tarreaue2226792022-04-11 18:04:33 +02001214 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER) && !(conn->flags & CO_FL_FDLESS)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001215 /* Some servers don't like reset on close */
Christopher Faulet897d6122021-12-17 17:28:35 +01001216 HA_ATOMIC_AND(&fdtab[conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001217 }
1218
1219 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1220 if (xprt_add_hs(conn) < 0)
1221 status = SF_ERR_RESOURCE;
1222 }
1223
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001224 if (conn_xprt_start(conn) < 0) {
1225 status = SF_ERR_RESOURCE;
1226 goto fail_check;
1227 }
1228
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001229 /* The mux may be initialized now if there isn't server attached to the
1230 * check (email alerts) or if there is a mux proto specified or if there
1231 * is no alpn.
1232 */
1233 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1234 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1235 const struct mux_ops *mux_ops;
1236
Christopher Faulet147b8c92021-04-10 09:00:38 +02001237 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001238 if (connect->mux_proto)
1239 mux_ops = connect->mux_proto->mux;
1240 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1241 mux_ops = check->mux_proto->mux;
1242 else {
1243 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1244 ? PROTO_MODE_HTTP
1245 : PROTO_MODE_TCP);
1246
1247 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1248 }
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001249 if (mux_ops && conn_install_mux(conn, mux_ops, check->cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001250 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001251 status = SF_ERR_INTERNAL;
1252 goto fail_check;
1253 }
1254 }
1255
Willy Tarreau51cd5952020-06-05 12:25:38 +02001256 fail_check:
1257 /* It can return one of :
1258 * - SF_ERR_NONE if everything's OK
1259 * - SF_ERR_SRVTO if there are no more servers
1260 * - SF_ERR_SRVCL if the connection was refused by the server
1261 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1262 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1263 * - SF_ERR_INTERNAL for any other purely internal errors
1264 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1265 * Note that we try to prevent the network stack from sending the ACK during the
1266 * connect() when a pure TCP check is used (without PROXY protocol).
1267 */
1268 switch (status) {
1269 case SF_ERR_NONE:
1270 /* we allow up to min(inter, timeout.connect) for a connection
1271 * to establish but only when timeout.check is set as it may be
1272 * to short for a full check otherwise
1273 */
1274 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1275
1276 if (proxy->timeout.check && proxy->timeout.connect) {
1277 int t_con = tick_add(now_ms, proxy->timeout.connect);
1278 t->expire = tick_first(t->expire, t_con);
1279 }
1280 break;
1281 case SF_ERR_SRVTO: /* ETIMEDOUT */
1282 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1283 case SF_ERR_PRXCOND:
1284 case SF_ERR_RESOURCE:
1285 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001286 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 +02001287 chk_report_conn_err(check, errno, 0);
1288 ret = TCPCHK_EVAL_STOP;
1289 goto out;
1290 }
1291
1292 /* don't do anything until the connection is established */
1293 if (conn->flags & CO_FL_WAIT_XPRT) {
1294 if (conn->mux) {
1295 if (next && next->action == TCPCHK_ACT_SEND)
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001296 conn->mux->subscribe(check->cs, SUB_RETRY_SEND, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001297 else
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001298 conn->mux->subscribe(check->cs, SUB_RETRY_RECV, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001299 }
1300 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001301 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001302 goto out;
1303 }
1304
1305 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001306 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001307 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001308 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001309 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001310
1311 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1312 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1313
Christopher Faulet147b8c92021-04-10 09:00:38 +02001314 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001315 return ret;
1316}
1317
1318/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1319 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1320 * TCPCHK_EVAL_STOP if an error occurred.
1321 */
1322enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1323{
1324 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1325 struct tcpcheck_send *send = &rule->send;
1326 struct conn_stream *cs = check->cs;
Christopher Faulet693b23b2022-02-28 09:09:05 +01001327 struct connection *conn = __cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001328 struct buffer *tmp = NULL;
1329 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001330 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001331
Christopher Faulet147b8c92021-04-10 09:00:38 +02001332 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1333
Christopher Fauletb381a502020-11-25 13:47:00 +01001334 if (check->state & CHK_ST_OUT_ALLOC) {
1335 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001336 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 +01001337 goto out;
1338 }
1339
1340 if (!check_get_buf(check, &check->bo)) {
1341 check->state |= CHK_ST_OUT_ALLOC;
1342 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001343 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 +01001344 goto out;
1345 }
1346
Christopher Faulet39066c22020-11-25 13:34:51 +01001347 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001348 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 +02001349 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 +01001350 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001351 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001352
Christopher Fauletb381a502020-11-25 13:47:00 +01001353 /* Always release input buffer when a new send is evaluated */
1354 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001355
1356 switch (send->type) {
1357 case TCPCHK_SEND_STRING:
1358 case TCPCHK_SEND_BINARY:
1359 if (istlen(send->data) >= b_size(&check->bo)) {
1360 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1361 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1362 tcpcheck_get_step_id(check, rule));
1363 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1364 ret = TCPCHK_EVAL_STOP;
1365 goto out;
1366 }
1367 b_putist(&check->bo, send->data);
1368 break;
1369 case TCPCHK_SEND_STRING_LF:
1370 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1371 if (!b_data(&check->bo))
1372 goto out;
1373 break;
1374 case TCPCHK_SEND_BINARY_LF: {
1375 int len = b_size(&check->bo);
1376
1377 tmp = alloc_trash_chunk();
1378 if (!tmp)
1379 goto error_lf;
1380 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1381 if (!b_data(tmp))
1382 goto out;
1383 tmp->area[tmp->data] = '\0';
1384 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1385 goto error_lf;
1386 check->bo.data = len;
1387 break;
1388 }
1389 case TCPCHK_SEND_HTTP: {
1390 struct htx_sl *sl;
1391 struct ist meth, uri, vsn, clen, body;
1392 unsigned int slflags = 0;
1393
1394 tmp = alloc_trash_chunk();
1395 if (!tmp)
1396 goto error_htx;
1397
1398 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1399 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1400 : http_known_methods[send->http.meth.meth]);
1401 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1402 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1403 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1404 }
1405 else
1406 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1407 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1408
1409 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1410 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1411 slflags |= HTX_SL_F_VER_11;
1412 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1413 if (!isttest(send->http.body))
1414 slflags |= HTX_SL_F_BODYLESS;
1415
1416 htx = htx_from_buf(&check->bo);
1417 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1418 if (!sl)
1419 goto error_htx;
1420 sl->info.req.meth = send->http.meth.meth;
1421 if (!http_update_host(htx, sl, uri))
1422 goto error_htx;
1423
1424 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1425 struct tcpcheck_http_hdr *hdr;
1426 struct ist hdr_value;
1427
1428 list_for_each_entry(hdr, &send->http.hdrs, list) {
1429 chunk_reset(tmp);
1430 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1431 if (!b_data(tmp))
1432 continue;
1433 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1434 if (!htx_add_header(htx, hdr->name, hdr_value))
1435 goto error_htx;
1436 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1437 if (!http_update_authority(htx, sl, hdr_value))
1438 goto error_htx;
1439 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001440 if (isteqi(hdr->name, ist("connection")))
1441 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001442 }
1443
1444 }
1445 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1446 chunk_reset(tmp);
1447 httpchk_build_status_header(check->server, tmp);
1448 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1449 goto error_htx;
1450 }
1451
1452
1453 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1454 chunk_reset(tmp);
1455 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1456 body = ist2(b_orig(tmp), b_data(tmp));
1457 }
1458 else
1459 body = send->http.body;
1460 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1461
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001462 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001463 !htx_add_header(htx, ist("Content-length"), clen))
1464 goto error_htx;
1465
1466
1467 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001468 (istlen(body) && !htx_add_data_atonce(htx, body)))
1469 goto error_htx;
1470
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001471 /* no more data are expected */
1472 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001473 htx_to_buf(htx, &check->bo);
1474 break;
1475 }
1476 case TCPCHK_SEND_UNDEF:
1477 /* Should never happen. */
1478 ret = TCPCHK_EVAL_STOP;
1479 goto out;
1480 };
1481
Christopher Faulet39066c22020-11-25 13:34:51 +01001482 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001483 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001484 if (conn->mux->snd_buf(cs, &check->bo,
1485 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Fauletb041b232022-03-24 10:27:02 +01001486 if ((conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001487 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001488 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 +02001489 goto out;
1490 }
1491 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001492 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 +01001493 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001494 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001495 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001496 goto out;
1497 }
1498
1499 out:
1500 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001501 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1502 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001503
1504 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001505 return ret;
1506
1507 error_htx:
1508 if (htx) {
1509 htx_reset(htx);
1510 htx_to_buf(htx, &check->bo);
1511 }
1512 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1513 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001514 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 +02001515 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1516 ret = TCPCHK_EVAL_STOP;
1517 goto out;
1518
1519 error_lf:
1520 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1521 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001522 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 +02001523 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1524 ret = TCPCHK_EVAL_STOP;
1525 goto out;
1526
1527}
1528
1529/* Try to receive data before evaluating a tcp-check expect rule. Returns
1530 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1531 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1532 * TCPCHK_EVAL_STOP if an error occurred.
1533 */
1534enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1535{
1536 struct conn_stream *cs = check->cs;
Christopher Faulet693b23b2022-02-28 09:09:05 +01001537 struct connection *conn = __cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001538 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1539 size_t max, read, cur_read = 0;
1540 int is_empty;
1541 int read_poll = MAX_READ_POLL_LOOPS;
1542
Christopher Faulet147b8c92021-04-10 09:00:38 +02001543 TRACE_ENTER(CHK_EV_RX_DATA, check);
1544
1545 if (check->wait_list.events & SUB_RETRY_RECV) {
1546 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001547 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001548 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001549
Christopher Fauletb041b232022-03-24 10:27:02 +01001550 if (cs->endp->flags & CS_EP_EOS)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001551 goto end_recv;
1552
Christopher Faulet147b8c92021-04-10 09:00:38 +02001553 if (check->state & CHK_ST_IN_ALLOC) {
1554 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001555 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001556 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001557
1558 if (!check_get_buf(check, &check->bi)) {
1559 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001560 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001561 goto wait_more_data;
1562 }
1563
Willy Tarreau51cd5952020-06-05 12:25:38 +02001564 /* errors on the connection and the conn-stream were already checked */
1565
1566 /* prepare to detect if the mux needs more room */
Christopher Fauletb041b232022-03-24 10:27:02 +01001567 cs->endp->flags &= ~CS_EP_WANT_ROOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001568
Christopher Fauletb041b232022-03-24 10:27:02 +01001569 while ((cs->endp->flags & CS_EP_RCV_MORE) ||
1570 (!(conn->flags & CO_FL_ERROR) && !(cs->endp->flags & (CS_EP_ERROR|CS_EP_EOS)))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001571 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1572 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1573 cur_read += read;
1574 if (!read ||
Christopher Fauletb041b232022-03-24 10:27:02 +01001575 (cs->endp->flags & CS_EP_WANT_ROOM) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001576 (--read_poll <= 0) ||
1577 (read < max && read >= global.tune.recv_enough))
1578 break;
1579 }
1580
1581 end_recv:
1582 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Christopher Fauletb041b232022-03-24 10:27:02 +01001583 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001584 /* Report network errors only if we got no other data. Otherwise
1585 * we'll let the upper layers decide whether the response is OK
1586 * or not. It is very common that an RST sent by the server is
1587 * reported as an error just after the last data chunk.
1588 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001589 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001590 goto stop;
1591 }
1592 if (!cur_read) {
Christopher Fauletb041b232022-03-24 10:27:02 +01001593 if (cs->endp->flags & CS_EP_EOI) {
Christopher Fauletd16e7dd2021-10-20 13:53:38 +02001594 /* If EOI is set, it means there is a response or an error */
1595 goto out;
1596 }
Christopher Fauletb041b232022-03-24 10:27:02 +01001597 if (!(cs->endp->flags & (CS_EP_WANT_ROOM|CS_EP_ERROR|CS_EP_EOS))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001598 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001599 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001600 goto wait_more_data;
1601 }
1602 if (is_empty) {
1603 int status;
1604
1605 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1606 tcpcheck_get_step_id(check, rule));
1607 if (rule->comment)
1608 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1609
Christopher Faulet147b8c92021-04-10 09:00:38 +02001610 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001611 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1612 set_server_check_status(check, status, trash.area);
1613 goto stop;
1614 }
1615 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001616 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001617
1618 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001619 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1620 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001621
1622 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001623 return ret;
1624
1625 stop:
1626 ret = TCPCHK_EVAL_STOP;
1627 goto out;
1628
1629 wait_more_data:
1630 ret = TCPCHK_EVAL_WAIT;
1631 goto out;
1632}
1633
1634/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1635 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1636 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1637 * error occurred.
1638 */
1639enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1640{
1641 struct htx *htx = htxbuf(&check->bi);
1642 struct htx_sl *sl;
1643 struct htx_blk *blk;
1644 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1645 struct tcpcheck_expect *expect = &rule->expect;
1646 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1647 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1648 struct ist desc = IST_NULL;
1649 int i, match, inverse;
1650
Christopher Faulet147b8c92021-04-10 09:00:38 +02001651 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1652
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001653 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001654
1655 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001656 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001657 status = HCHK_STATUS_L7RSP;
1658 goto error;
1659 }
1660
1661 if (htx_is_empty(htx)) {
1662 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001663 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001664 status = HCHK_STATUS_L7RSP;
1665 goto error;
1666 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001667 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001668 goto wait_more_data;
1669 }
1670
1671 sl = http_get_stline(htx);
1672 check->code = sl->info.res.status;
1673
1674 if (check->server &&
1675 (check->server->proxy->options & PR_O_DISABLE404) &&
1676 (check->server->next_state != SRV_ST_STOPPED) &&
1677 (check->code == 404)) {
1678 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001679 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001680 goto out;
1681 }
1682
1683 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1684 /* Make GCC happy ; initialize match to a failure state. */
1685 match = inverse;
1686 status = expect->err_status;
1687
1688 switch (expect->type) {
1689 case TCPCHK_EXPECT_HTTP_STATUS:
1690 match = 0;
1691 for (i = 0; i < expect->codes.num; i++) {
1692 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1693 sl->info.res.status <= expect->codes.codes[i][1]) {
1694 match = 1;
1695 break;
1696 }
1697 }
1698
1699 /* Set status and description in case of error */
1700 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1701 if (LIST_ISEMPTY(&expect->onerror_fmt))
1702 desc = htx_sl_res_reason(sl);
1703 break;
1704 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1705 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1706
1707 /* Set status and description in case of error */
1708 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1709 if (LIST_ISEMPTY(&expect->onerror_fmt))
1710 desc = htx_sl_res_reason(sl);
1711 break;
1712
1713 case TCPCHK_EXPECT_HTTP_HEADER: {
1714 struct http_hdr_ctx ctx;
1715 struct ist npat, vpat, value;
1716 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1717
1718 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1719 nbuf = alloc_trash_chunk();
1720 if (!nbuf) {
1721 status = HCHK_STATUS_L7RSP;
1722 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001723 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001724 goto error;
1725 }
1726 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1727 if (!b_data(nbuf)) {
1728 status = HCHK_STATUS_L7RSP;
1729 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001730 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001731 goto error;
1732 }
1733 npat = ist2(b_orig(nbuf), b_data(nbuf));
1734 }
1735 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1736 npat = expect->hdr.name;
1737
1738 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1739 vbuf = alloc_trash_chunk();
1740 if (!vbuf) {
1741 status = HCHK_STATUS_L7RSP;
1742 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001743 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001744 goto error;
1745 }
1746 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1747 if (!b_data(vbuf)) {
1748 status = HCHK_STATUS_L7RSP;
1749 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001750 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001751 goto error;
1752 }
1753 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1754 }
1755 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1756 vpat = expect->hdr.value;
1757
1758 match = 0;
1759 ctx.blk = NULL;
1760 while (1) {
1761 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1762 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1763 if (!http_find_str_header(htx, npat, &ctx, full))
1764 goto end_of_match;
1765 break;
1766 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1767 if (!http_find_pfx_header(htx, npat, &ctx, full))
1768 goto end_of_match;
1769 break;
1770 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1771 if (!http_find_sfx_header(htx, npat, &ctx, full))
1772 goto end_of_match;
1773 break;
1774 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1775 if (!http_find_sub_header(htx, npat, &ctx, full))
1776 goto end_of_match;
1777 break;
1778 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1779 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1780 goto end_of_match;
1781 break;
1782 default:
1783 /* should never happen */
1784 goto end_of_match;
1785 }
1786
1787 /* A header has matched the name pattern, let's test its
1788 * value now (always defined from there). If there is no
1789 * value pattern, it is a good match.
1790 */
1791
1792 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1793 match = 1;
1794 goto end_of_match;
1795 }
1796
1797 value = ctx.value;
1798 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1799 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1800 if (isteq(value, vpat)) {
1801 match = 1;
1802 goto end_of_match;
1803 }
1804 break;
1805 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1806 if (istlen(value) < istlen(vpat))
1807 break;
1808 value = ist2(istptr(value), istlen(vpat));
1809 if (isteq(value, vpat)) {
1810 match = 1;
1811 goto end_of_match;
1812 }
1813 break;
1814 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1815 if (istlen(value) < istlen(vpat))
1816 break;
Tim Duesterhus4c8f75f2021-11-06 15:14:44 +01001817 value = ist2(istend(value) - istlen(vpat), istlen(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001818 if (isteq(value, vpat)) {
1819 match = 1;
1820 goto end_of_match;
1821 }
1822 break;
1823 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1824 if (isttest(istist(value, vpat))) {
1825 match = 1;
1826 goto end_of_match;
1827 }
1828 break;
1829 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1830 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1831 match = 1;
1832 goto end_of_match;
1833 }
1834 break;
1835 }
1836 }
1837
1838 end_of_match:
1839 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1840 if (LIST_ISEMPTY(&expect->onerror_fmt))
1841 desc = htx_sl_res_reason(sl);
1842 break;
1843 }
1844
1845 case TCPCHK_EXPECT_HTTP_BODY:
1846 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1847 case TCPCHK_EXPECT_HTTP_BODY_LF:
1848 match = 0;
1849 chunk_reset(&trash);
1850 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1851 enum htx_blk_type type = htx_get_blk_type(blk);
1852
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001853 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001854 break;
1855 if (type == HTX_BLK_DATA) {
1856 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1857 break;
1858 }
1859 }
1860
1861 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001862 if (!last_read) {
1863 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001864 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001865 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001866 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1867 if (LIST_ISEMPTY(&expect->onerror_fmt))
1868 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001869 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001870 goto error;
1871 }
1872
1873 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1874 tmp = alloc_trash_chunk();
1875 if (!tmp) {
1876 status = HCHK_STATUS_L7RSP;
1877 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001878 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001879 goto error;
1880 }
1881 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1882 if (!b_data(tmp)) {
1883 status = HCHK_STATUS_L7RSP;
1884 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001885 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001886 goto error;
1887 }
1888 }
1889
1890 if (!last_read &&
1891 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1892 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1893 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1894 ret = TCPCHK_EVAL_WAIT;
1895 goto out;
1896 }
1897
1898 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1899 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1900 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1901 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1902 else
1903 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1904
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001905 /* Wait for more data on mismatch only if no minimum is defined (-1),
1906 * otherwise the absence of match is already conclusive.
1907 */
1908 if (!match && !last_read && (expect->min_recv == -1)) {
1909 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001910 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001911 goto out;
1912 }
1913
Willy Tarreau51cd5952020-06-05 12:25:38 +02001914 /* Set status and description in case of error */
1915 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1916 if (LIST_ISEMPTY(&expect->onerror_fmt))
1917 desc = (inverse
1918 ? ist("HTTP check matched unwanted content")
1919 : ist("HTTP content check did not match"));
1920 break;
1921
1922
1923 default:
1924 /* should never happen */
1925 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1926 goto error;
1927 }
1928
Christopher Faulet147b8c92021-04-10 09:00:38 +02001929 if (!(match ^ inverse)) {
1930 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001931 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001932 }
1933
1934 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001935
1936 out:
1937 free_trash_chunk(tmp);
1938 free_trash_chunk(nbuf);
1939 free_trash_chunk(vbuf);
1940 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001941 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001942 return ret;
1943
1944 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001945 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001946 ret = TCPCHK_EVAL_STOP;
1947 msg = alloc_trash_chunk();
1948 if (msg)
1949 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1950 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1951 goto out;
1952
1953 wait_more_data:
1954 ret = TCPCHK_EVAL_WAIT;
1955 goto out;
1956}
1957
1958/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1959 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1960 * if an error occurred.
1961 */
1962enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1963{
1964 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1965 struct tcpcheck_expect *expect = &rule->expect;
1966 struct buffer *msg = NULL, *tmp = NULL;
1967 struct ist desc = IST_NULL;
1968 enum healthcheck_status status;
1969 int match, inverse;
1970
Christopher Faulet147b8c92021-04-10 09:00:38 +02001971 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1972
Willy Tarreau51cd5952020-06-05 12:25:38 +02001973 last_read |= b_full(&check->bi);
1974
1975 /* The current expect might need more data than the previous one, check again
1976 * that the minimum amount data required to match is respected.
1977 */
1978 if (!last_read) {
1979 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1980 (b_data(&check->bi) < istlen(expect->data))) {
1981 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001982 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001983 goto out;
1984 }
1985 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1986 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001987 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001988 goto out;
1989 }
1990 }
1991
1992 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1993 /* Make GCC happy ; initialize match to a failure state. */
1994 match = inverse;
1995 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1996
1997 switch (expect->type) {
1998 case TCPCHK_EXPECT_STRING:
1999 case TCPCHK_EXPECT_BINARY:
2000 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2001 break;
2002 case TCPCHK_EXPECT_STRING_REGEX:
2003 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2004 break;
2005
2006 case TCPCHK_EXPECT_BINARY_REGEX:
2007 chunk_reset(&trash);
2008 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2009 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2010 break;
2011
2012 case TCPCHK_EXPECT_STRING_LF:
2013 case TCPCHK_EXPECT_BINARY_LF:
2014 match = 0;
2015 tmp = alloc_trash_chunk();
2016 if (!tmp) {
2017 status = HCHK_STATUS_L7RSP;
2018 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002019 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002020 goto error;
2021 }
2022 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2023 if (!b_data(tmp)) {
2024 status = HCHK_STATUS_L7RSP;
2025 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002026 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002027 goto error;
2028 }
2029 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2030 int len = tmp->data;
2031 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2032 status = HCHK_STATUS_L7RSP;
2033 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002034 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002035 goto error;
2036 }
2037 tmp->data = len;
2038 }
2039 if (b_data(&check->bi) < tmp->data) {
2040 if (!last_read) {
2041 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002042 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002043 goto out;
2044 }
2045 break;
2046 }
2047 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2048 break;
2049
2050 case TCPCHK_EXPECT_CUSTOM:
2051 if (expect->custom)
2052 ret = expect->custom(check, rule, last_read);
2053 goto out;
2054 default:
2055 /* Should never happen. */
2056 ret = TCPCHK_EVAL_STOP;
2057 goto out;
2058 }
2059
2060
2061 /* Wait for more data on mismatch only if no minimum is defined (-1),
2062 * otherwise the absence of match is already conclusive.
2063 */
2064 if (!match && !last_read && (expect->min_recv == -1)) {
2065 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002066 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002067 goto out;
2068 }
2069
2070 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002071 if (match ^ inverse) {
2072 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002073 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002074 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002075
2076 error:
2077 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002078 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002079 ret = TCPCHK_EVAL_STOP;
2080 msg = alloc_trash_chunk();
2081 if (msg)
2082 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2083 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2084 free_trash_chunk(msg);
2085
2086 out:
2087 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002088 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002089 return ret;
2090}
2091
2092/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2093 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2094 * waits.
2095 */
2096enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2097{
2098 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2099 struct act_rule *act_rule;
2100 enum act_return act_ret;
2101
2102 act_rule =rule->action_kw.rule;
2103 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2104 if (act_ret != ACT_RET_CONT) {
2105 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2106 tcpcheck_get_step_id(check, rule));
2107 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2108 ret = TCPCHK_EVAL_STOP;
2109 }
2110
2111 return ret;
2112}
2113
2114/* Executes a tcp-check ruleset. Note that this is called both from the
2115 * connection's wake() callback and from the check scheduling task. It returns
2116 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2117 * presenting the risk of an fd replacement.
2118 *
2119 * Please do NOT place any return statement in this function and only leave
2120 * via the out_end_tcpcheck label after setting retcode.
2121 */
2122int tcpcheck_main(struct check *check)
2123{
2124 struct tcpcheck_rule *rule;
2125 struct conn_stream *cs = check->cs;
2126 struct connection *conn = cs_conn(cs);
2127 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002128 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002129 enum tcpcheck_eval_ret eval_ret;
2130
2131 /* here, we know that the check is complete or that it failed */
2132 if (check->result != CHK_RES_UNKNOWN)
2133 goto out;
2134
Christopher Faulet147b8c92021-04-10 09:00:38 +02002135 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2136
Willy Tarreau51cd5952020-06-05 12:25:38 +02002137 /* Note: the conn-stream and the connection may only be undefined before
2138 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002139 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002140 */
2141
2142 /* 1- check for connection error, if any */
Christopher Fauletb041b232022-03-24 10:27:02 +01002143 if ((conn && conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002144 goto out_end_tcpcheck;
2145
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002146 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002147 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002148 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002149 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002150 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2151 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002152
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002153 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002154 * tcp-check variables */
2155 else {
2156 struct tcpcheck_var *var;
2157
2158 /* First evaluation, create a session */
2159 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2160 if (!check->sess) {
2161 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002162 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002163 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2164 goto out_end_tcpcheck;
2165 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002166 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002167 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2168
2169 /* Preset tcp-check variables */
2170 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2171 struct sample smp;
2172
2173 memset(&smp, 0, sizeof(smp));
2174 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2175 smp.data = var->data;
2176 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2177 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002178 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002179 }
2180
2181 /* Now evaluate the tcp-check rules */
2182
2183 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2184 check->code = 0;
2185 switch (rule->action) {
2186 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002187 /* Not the first connection, release it first */
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002188 if (cs_conn(cs) && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002189 check->state |= CHK_ST_CLOSE_CONN;
2190 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002191 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002192
2193 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002194
2195 /* We are still waiting the connection gets closed */
2196 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002197 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002198 eval_ret = TCPCHK_EVAL_WAIT;
2199 break;
2200 }
2201
Christopher Faulet147b8c92021-04-10 09:00:38 +02002202 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002203 eval_ret = tcpcheck_eval_connect(check, rule);
2204
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002205 /* Refresh connection */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002206 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002207 last_read = 0;
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002208 must_read = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002209 break;
2210 case TCPCHK_ACT_SEND:
2211 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002212 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002213 eval_ret = tcpcheck_eval_send(check, rule);
2214 must_read = 1;
2215 break;
2216 case TCPCHK_ACT_EXPECT:
2217 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002218 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002219 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002220 eval_ret = tcpcheck_eval_recv(check, rule);
2221 if (eval_ret == TCPCHK_EVAL_STOP)
2222 goto out_end_tcpcheck;
2223 else if (eval_ret == TCPCHK_EVAL_WAIT)
2224 goto out;
Christopher Fauletb041b232022-03-24 10:27:02 +01002225 last_read = ((conn->flags & CO_FL_ERROR) || (cs->endp->flags & (CS_EP_ERROR|CS_EP_EOS)));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002226 must_read = 0;
2227 }
2228
2229 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2230 ? tcpcheck_eval_expect_http(check, rule, last_read)
2231 : tcpcheck_eval_expect(check, rule, last_read));
2232
2233 if (eval_ret == TCPCHK_EVAL_WAIT) {
2234 check->current_step = rule->expect.head;
2235 if (!(check->wait_list.events & SUB_RETRY_RECV))
2236 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2237 }
2238 break;
2239 case TCPCHK_ACT_ACTION_KW:
2240 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002241 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002242 eval_ret = tcpcheck_eval_action_kw(check, rule);
2243 break;
2244 default:
2245 /* Otherwise, just go to the next one and don't update
2246 * the current step
2247 */
2248 eval_ret = TCPCHK_EVAL_CONTINUE;
2249 break;
2250 }
2251
2252 switch (eval_ret) {
2253 case TCPCHK_EVAL_CONTINUE:
2254 break;
2255 case TCPCHK_EVAL_WAIT:
2256 goto out;
2257 case TCPCHK_EVAL_STOP:
2258 goto out_end_tcpcheck;
2259 }
2260 }
2261
2262 /* All rules was evaluated */
2263 if (check->current_step) {
2264 rule = check->current_step;
2265
Christopher Faulet147b8c92021-04-10 09:00:38 +02002266 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2267
Willy Tarreau51cd5952020-06-05 12:25:38 +02002268 if (rule->action == TCPCHK_ACT_EXPECT) {
2269 struct buffer *msg;
2270 enum healthcheck_status status;
2271
2272 if (check->server &&
2273 (check->server->proxy->options & PR_O_DISABLE404) &&
2274 (check->server->next_state != SRV_ST_STOPPED) &&
2275 (check->code == 404)) {
2276 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002277 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002278 goto out_end_tcpcheck;
2279 }
2280
2281 msg = alloc_trash_chunk();
2282 if (msg)
2283 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2284 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2285 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2286 free_trash_chunk(msg);
2287 }
2288 else if (rule->action == TCPCHK_ACT_CONNECT) {
2289 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2290 enum healthcheck_status status = HCHK_STATUS_L4OK;
2291#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002292 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002293 status = HCHK_STATUS_L6OK;
2294#endif
2295 set_server_check_status(check, status, msg);
2296 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002297 else
2298 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002299 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002300 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002301 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002302 }
2303 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002304
2305 out_end_tcpcheck:
Christopher Fauletb041b232022-03-24 10:27:02 +01002306 if ((conn && conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002307 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002308 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002309 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002310
Christopher Fauletb381a502020-11-25 13:47:00 +01002311 /* the tcpcheck is finished, release in/out buffer now */
2312 check_release_buf(check, &check->bi);
2313 check_release_buf(check, &check->bo);
2314
Willy Tarreau51cd5952020-06-05 12:25:38 +02002315 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002316 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002317 return retcode;
2318}
2319
Willy Tarreaua631b862022-03-02 14:54:44 +01002320void tcp_check_keywords_register(struct action_kw_list *kw_list)
2321{
2322 LIST_APPEND(&tcp_check_keywords.list, &kw_list->list);
2323}
Willy Tarreau51cd5952020-06-05 12:25:38 +02002324
2325/**************************************************************************/
2326/******************* Internals to parse tcp-check rules *******************/
2327/**************************************************************************/
2328struct action_kw_list tcp_check_keywords = {
2329 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2330};
2331
2332/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2333 * returned on error.
2334 */
2335struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2336 struct list *rules, struct action_kw *kw,
2337 const char *file, int line, char **errmsg)
2338{
2339 struct tcpcheck_rule *chk = NULL;
2340 struct act_rule *actrule = NULL;
2341
Willy Tarreaud535f802021-10-11 08:49:26 +02002342 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002343 if (!actrule) {
2344 memprintf(errmsg, "out of memory");
2345 goto error;
2346 }
2347 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002348
2349 cur_arg++;
2350 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2351 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2352 goto error;
2353 }
2354
2355 chk = calloc(1, sizeof(*chk));
2356 if (!chk) {
2357 memprintf(errmsg, "out of memory");
2358 goto error;
2359 }
2360 chk->action = TCPCHK_ACT_ACTION_KW;
2361 chk->action_kw.rule = actrule;
2362 return chk;
2363
2364 error:
2365 free(actrule);
2366 return NULL;
2367}
2368
2369/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2370 * returned on error.
2371 */
2372struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2373 const char *file, int line, char **errmsg)
2374{
2375 struct tcpcheck_rule *chk = NULL;
2376 struct sockaddr_storage *sk = NULL;
2377 char *comment = NULL, *sni = NULL, *alpn = NULL;
2378 struct sample_expr *port_expr = NULL;
2379 const struct mux_proto_list *mux_proto = NULL;
2380 unsigned short conn_opts = 0;
2381 long port = 0;
2382 int alpn_len = 0;
2383
2384 list_for_each_entry(chk, rules, list) {
2385 if (chk->action == TCPCHK_ACT_CONNECT)
2386 break;
2387 if (chk->action == TCPCHK_ACT_COMMENT ||
2388 chk->action == TCPCHK_ACT_ACTION_KW ||
2389 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2390 continue;
2391
2392 memprintf(errmsg, "first step MUST also be a 'connect', "
2393 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2394 "when there is a 'connect' step in the tcp-check ruleset");
2395 goto error;
2396 }
2397
2398 cur_arg++;
2399 while (*(args[cur_arg])) {
2400 if (strcmp(args[cur_arg], "default") == 0)
2401 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2402 else if (strcmp(args[cur_arg], "addr") == 0) {
2403 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002404
2405 if (!*(args[cur_arg+1])) {
2406 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2407 goto error;
2408 }
2409
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002410 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2411 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002412 if (!sk) {
2413 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2414 goto error;
2415 }
2416
Willy Tarreau51cd5952020-06-05 12:25:38 +02002417 cur_arg++;
2418 }
2419 else if (strcmp(args[cur_arg], "port") == 0) {
2420 const char *p, *end;
2421
2422 if (!*(args[cur_arg+1])) {
2423 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2424 goto error;
2425 }
2426 cur_arg++;
2427
2428 port = 0;
2429 release_sample_expr(port_expr);
2430 p = args[cur_arg]; end = p + strlen(p);
2431 port = read_uint(&p, end);
2432 if (p != end) {
2433 int idx = 0;
2434
2435 px->conf.args.ctx = ARGC_SRV;
2436 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002437 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002438
2439 if (!port_expr) {
2440 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2441 goto error;
2442 }
2443 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2444 memprintf(errmsg, "error detected while parsing port expression : "
2445 " fetch method '%s' extracts information from '%s', "
2446 "none of which is available here.\n",
2447 args[cur_arg], sample_src_names(port_expr->fetch->use));
2448 goto error;
2449 }
2450 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2451 }
2452 else if (port > 65535 || port < 1) {
2453 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2454 args[cur_arg]);
2455 goto error;
2456 }
2457 }
2458 else if (strcmp(args[cur_arg], "proto") == 0) {
2459 if (!*(args[cur_arg+1])) {
2460 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2461 goto error;
2462 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002463 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002464 if (!mux_proto) {
2465 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2466 goto error;
2467 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002468
2469 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2470 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2471 goto error;
2472 }
2473 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2474 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2475 goto error;
2476 }
2477
Willy Tarreau51cd5952020-06-05 12:25:38 +02002478 cur_arg++;
2479 }
2480 else if (strcmp(args[cur_arg], "comment") == 0) {
2481 if (!*(args[cur_arg+1])) {
2482 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2483 goto error;
2484 }
2485 cur_arg++;
2486 free(comment);
2487 comment = strdup(args[cur_arg]);
2488 if (!comment) {
2489 memprintf(errmsg, "out of memory");
2490 goto error;
2491 }
2492 }
2493 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2494 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2495 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2496 conn_opts |= TCPCHK_OPT_SOCKS4;
2497 else if (strcmp(args[cur_arg], "linger") == 0)
2498 conn_opts |= TCPCHK_OPT_LINGER;
2499#ifdef USE_OPENSSL
2500 else if (strcmp(args[cur_arg], "ssl") == 0) {
2501 px->options |= PR_O_TCPCHK_SSL;
2502 conn_opts |= TCPCHK_OPT_SSL;
2503 }
2504 else if (strcmp(args[cur_arg], "sni") == 0) {
2505 if (!*(args[cur_arg+1])) {
2506 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2507 goto error;
2508 }
2509 cur_arg++;
2510 free(sni);
2511 sni = strdup(args[cur_arg]);
2512 if (!sni) {
2513 memprintf(errmsg, "out of memory");
2514 goto error;
2515 }
2516 }
2517 else if (strcmp(args[cur_arg], "alpn") == 0) {
2518#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2519 free(alpn);
2520 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2521 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2522 goto error;
2523 }
2524 cur_arg++;
2525#else
2526 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2527 goto error;
2528#endif
2529 }
2530#endif /* USE_OPENSSL */
2531
2532 else {
2533 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2534#ifdef USE_OPENSSL
2535 ", 'ssl', 'sni', 'alpn'"
2536#endif /* USE_OPENSSL */
2537 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2538 args[cur_arg]);
2539 goto error;
2540 }
2541 cur_arg++;
2542 }
2543
2544 chk = calloc(1, sizeof(*chk));
2545 if (!chk) {
2546 memprintf(errmsg, "out of memory");
2547 goto error;
2548 }
2549 chk->action = TCPCHK_ACT_CONNECT;
2550 chk->comment = comment;
2551 chk->connect.port = port;
2552 chk->connect.options = conn_opts;
2553 chk->connect.sni = sni;
2554 chk->connect.alpn = alpn;
2555 chk->connect.alpn_len= alpn_len;
2556 chk->connect.port_expr= port_expr;
2557 chk->connect.mux_proto= mux_proto;
2558 if (sk)
2559 chk->connect.addr = *sk;
2560 return chk;
2561
2562 error:
2563 free(alpn);
2564 free(sni);
2565 free(comment);
2566 release_sample_expr(port_expr);
2567 return NULL;
2568}
2569
2570/* Parses and creates a tcp-check send rule. NULL is returned on error */
2571struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2572 const char *file, int line, char **errmsg)
2573{
2574 struct tcpcheck_rule *chk = NULL;
2575 char *comment = NULL, *data = NULL;
2576 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2577
2578 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2579 type = TCPCHK_SEND_BINARY_LF;
2580 else if (strcmp(args[cur_arg], "send-binary") == 0)
2581 type = TCPCHK_SEND_BINARY;
2582 else if (strcmp(args[cur_arg], "send-lf") == 0)
2583 type = TCPCHK_SEND_STRING_LF;
2584 else if (strcmp(args[cur_arg], "send") == 0)
2585 type = TCPCHK_SEND_STRING;
2586
2587 if (!*(args[cur_arg+1])) {
2588 memprintf(errmsg, "'%s' expects a %s as argument",
2589 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2590 goto error;
2591 }
2592
2593 data = args[cur_arg+1];
2594
2595 cur_arg += 2;
2596 while (*(args[cur_arg])) {
2597 if (strcmp(args[cur_arg], "comment") == 0) {
2598 if (!*(args[cur_arg+1])) {
2599 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2600 goto error;
2601 }
2602 cur_arg++;
2603 free(comment);
2604 comment = strdup(args[cur_arg]);
2605 if (!comment) {
2606 memprintf(errmsg, "out of memory");
2607 goto error;
2608 }
2609 }
2610 else {
2611 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2612 args[cur_arg]);
2613 goto error;
2614 }
2615 cur_arg++;
2616 }
2617
2618 chk = calloc(1, sizeof(*chk));
2619 if (!chk) {
2620 memprintf(errmsg, "out of memory");
2621 goto error;
2622 }
2623 chk->action = TCPCHK_ACT_SEND;
2624 chk->comment = comment;
2625 chk->send.type = type;
2626
2627 switch (chk->send.type) {
2628 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002629 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002630 if (!isttest(chk->send.data)) {
2631 memprintf(errmsg, "out of memory");
2632 goto error;
2633 }
2634 break;
2635 case TCPCHK_SEND_BINARY: {
2636 int len = chk->send.data.len;
2637 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2638 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2639 goto error;
2640 }
2641 chk->send.data.len = len;
2642 break;
2643 }
2644 case TCPCHK_SEND_STRING_LF:
2645 case TCPCHK_SEND_BINARY_LF:
2646 LIST_INIT(&chk->send.fmt);
2647 px->conf.args.ctx = ARGC_SRV;
2648 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2649 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2650 goto error;
2651 }
2652 break;
2653 case TCPCHK_SEND_HTTP:
2654 case TCPCHK_SEND_UNDEF:
2655 goto error;
2656 }
2657
2658 return chk;
2659
2660 error:
2661 free(chk);
2662 free(comment);
2663 return NULL;
2664}
2665
2666/* Parses and creates a http-check send rule. NULL is returned on error */
2667struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2668 const char *file, int line, char **errmsg)
2669{
2670 struct tcpcheck_rule *chk = NULL;
2671 struct tcpcheck_http_hdr *hdr = NULL;
2672 struct http_hdr hdrs[global.tune.max_http_hdr];
2673 char *meth = NULL, *uri = NULL, *vsn = NULL;
2674 char *body = NULL, *comment = NULL;
2675 unsigned int flags = 0;
2676 int i = 0, host_hdr = -1;
2677
2678 cur_arg++;
2679 while (*(args[cur_arg])) {
2680 if (strcmp(args[cur_arg], "meth") == 0) {
2681 if (!*(args[cur_arg+1])) {
2682 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2683 goto error;
2684 }
2685 cur_arg++;
2686 meth = args[cur_arg];
2687 }
2688 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2689 if (!*(args[cur_arg+1])) {
2690 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2691 goto error;
2692 }
2693 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2694 if (strcmp(args[cur_arg], "uri-lf") == 0)
2695 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2696 cur_arg++;
2697 uri = args[cur_arg];
2698 }
2699 else if (strcmp(args[cur_arg], "ver") == 0) {
2700 if (!*(args[cur_arg+1])) {
2701 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2702 goto error;
2703 }
2704 cur_arg++;
2705 vsn = args[cur_arg];
2706 }
2707 else if (strcmp(args[cur_arg], "hdr") == 0) {
2708 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2709 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2710 goto error;
2711 }
2712
2713 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2714 if (host_hdr >= 0) {
2715 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2716 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2717 goto error;
2718 }
2719 host_hdr = i;
2720 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002721 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002722 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2723 goto skip_hdr;
2724
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002725 hdrs[i].n = ist(args[cur_arg + 1]);
2726 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002727 i++;
2728 skip_hdr:
2729 cur_arg += 2;
2730 }
2731 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2732 if (!*(args[cur_arg+1])) {
2733 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2734 goto error;
2735 }
2736 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2737 if (strcmp(args[cur_arg], "body-lf") == 0)
2738 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2739 cur_arg++;
2740 body = args[cur_arg];
2741 }
2742 else if (strcmp(args[cur_arg], "comment") == 0) {
2743 if (!*(args[cur_arg+1])) {
2744 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2745 goto error;
2746 }
2747 cur_arg++;
2748 free(comment);
2749 comment = strdup(args[cur_arg]);
2750 if (!comment) {
2751 memprintf(errmsg, "out of memory");
2752 goto error;
2753 }
2754 }
2755 else {
2756 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2757 " but got '%s' as argument.", args[cur_arg]);
2758 goto error;
2759 }
2760 cur_arg++;
2761 }
2762
2763 hdrs[i].n = hdrs[i].v = IST_NULL;
2764
2765 chk = calloc(1, sizeof(*chk));
2766 if (!chk) {
2767 memprintf(errmsg, "out of memory");
2768 goto error;
2769 }
2770 chk->action = TCPCHK_ACT_SEND;
2771 chk->comment = comment; comment = NULL;
2772 chk->send.type = TCPCHK_SEND_HTTP;
2773 chk->send.http.flags = flags;
2774 LIST_INIT(&chk->send.http.hdrs);
2775
2776 if (meth) {
2777 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2778 chk->send.http.meth.str.area = strdup(meth);
2779 chk->send.http.meth.str.data = strlen(meth);
2780 if (!chk->send.http.meth.str.area) {
2781 memprintf(errmsg, "out of memory");
2782 goto error;
2783 }
2784 }
2785 if (uri) {
2786 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2787 LIST_INIT(&chk->send.http.uri_fmt);
2788 px->conf.args.ctx = ARGC_SRV;
2789 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2790 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2791 goto error;
2792 }
2793 }
2794 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002795 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002796 if (!isttest(chk->send.http.uri)) {
2797 memprintf(errmsg, "out of memory");
2798 goto error;
2799 }
2800 }
2801 }
2802 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002803 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002804 if (!isttest(chk->send.http.vsn)) {
2805 memprintf(errmsg, "out of memory");
2806 goto error;
2807 }
2808 }
2809 for (i = 0; istlen(hdrs[i].n); i++) {
2810 hdr = calloc(1, sizeof(*hdr));
2811 if (!hdr) {
2812 memprintf(errmsg, "out of memory");
2813 goto error;
2814 }
2815 LIST_INIT(&hdr->value);
2816 hdr->name = istdup(hdrs[i].n);
2817 if (!isttest(hdr->name)) {
2818 memprintf(errmsg, "out of memory");
2819 goto error;
2820 }
2821
2822 ist0(hdrs[i].v);
2823 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2824 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002825 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002826 hdr = NULL;
2827 }
2828
2829 if (body) {
2830 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2831 LIST_INIT(&chk->send.http.body_fmt);
2832 px->conf.args.ctx = ARGC_SRV;
2833 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2834 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2835 goto error;
2836 }
2837 }
2838 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002839 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002840 if (!isttest(chk->send.http.body)) {
2841 memprintf(errmsg, "out of memory");
2842 goto error;
2843 }
2844 }
2845 }
2846
2847 return chk;
2848
2849 error:
2850 free_tcpcheck_http_hdr(hdr);
2851 free_tcpcheck(chk, 0);
2852 free(comment);
2853 return NULL;
2854}
2855
2856/* Parses and creates a http-check comment rule. NULL is returned on error */
2857struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2858 const char *file, int line, char **errmsg)
2859{
2860 struct tcpcheck_rule *chk = NULL;
2861 char *comment = NULL;
2862
2863 if (!*(args[cur_arg+1])) {
2864 memprintf(errmsg, "expects a string as argument");
2865 goto error;
2866 }
2867 cur_arg++;
2868 comment = strdup(args[cur_arg]);
2869 if (!comment) {
2870 memprintf(errmsg, "out of memory");
2871 goto error;
2872 }
2873
2874 chk = calloc(1, sizeof(*chk));
2875 if (!chk) {
2876 memprintf(errmsg, "out of memory");
2877 goto error;
2878 }
2879 chk->action = TCPCHK_ACT_COMMENT;
2880 chk->comment = comment;
2881 return chk;
2882
2883 error:
2884 free(comment);
2885 return NULL;
2886}
2887
2888/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2889 * on error. <proto> is set to the right protocol flags (covered by the
2890 * TCPCHK_RULES_PROTO_CHK mask).
2891 */
2892struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2893 struct list *rules, unsigned int proto,
2894 const char *file, int line, char **errmsg)
2895{
2896 struct tcpcheck_rule *prev_check, *chk = NULL;
2897 struct sample_expr *status_expr = NULL;
2898 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2899 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2900 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2901 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2902 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2903 unsigned int flags = 0;
2904 long min_recv = -1;
2905 int inverse = 0;
2906
2907 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2908 if (!*(args[cur_arg+1])) {
2909 memprintf(errmsg, "expects at least a matching pattern as arguments");
2910 goto error;
2911 }
2912
2913 cur_arg++;
2914 while (*(args[cur_arg])) {
2915 int in_pattern = 0;
2916
2917 rescan:
2918 if (strcmp(args[cur_arg], "min-recv") == 0) {
2919 if (in_pattern) {
2920 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2921 goto error;
2922 }
2923 if (!*(args[cur_arg+1])) {
2924 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2925 goto error;
2926 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002927 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002928 cur_arg++;
2929 min_recv = atol(args[cur_arg]);
2930 if (min_recv < -1 || min_recv > INT_MAX) {
2931 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2932 goto error;
2933 }
2934 }
2935 else if (*(args[cur_arg]) == '!') {
2936 in_pattern = 1;
2937 while (*(args[cur_arg]) == '!') {
2938 inverse = !inverse;
2939 args[cur_arg]++;
2940 }
2941 if (!*(args[cur_arg]))
2942 cur_arg++;
2943 goto rescan;
2944 }
2945 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2946 if (type != TCPCHK_EXPECT_UNDEF) {
2947 memprintf(errmsg, "only on pattern expected");
2948 goto error;
2949 }
2950 if (proto != TCPCHK_RULES_HTTP_CHK)
2951 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2952 else
2953 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2954
2955 if (!*(args[cur_arg+1])) {
2956 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2957 goto error;
2958 }
2959 cur_arg++;
2960 pattern = args[cur_arg];
2961 }
2962 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2963 if (proto == TCPCHK_RULES_HTTP_CHK)
2964 goto bad_http_kw;
2965 if (type != TCPCHK_EXPECT_UNDEF) {
2966 memprintf(errmsg, "only on pattern expected");
2967 goto error;
2968 }
2969 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2970
2971 if (!*(args[cur_arg+1])) {
2972 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2973 goto error;
2974 }
2975 cur_arg++;
2976 pattern = args[cur_arg];
2977 }
2978 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2979 if (type != TCPCHK_EXPECT_UNDEF) {
2980 memprintf(errmsg, "only on pattern expected");
2981 goto error;
2982 }
2983 if (proto != TCPCHK_RULES_HTTP_CHK)
2984 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2985 else {
2986 if (*(args[cur_arg]) != 's')
2987 goto bad_http_kw;
2988 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2989 }
2990
2991 if (!*(args[cur_arg+1])) {
2992 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2993 goto error;
2994 }
2995 cur_arg++;
2996 pattern = args[cur_arg];
2997 }
2998 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
2999 if (proto != TCPCHK_RULES_HTTP_CHK)
3000 goto bad_tcp_kw;
3001 if (type != TCPCHK_EXPECT_UNDEF) {
3002 memprintf(errmsg, "only on pattern expected");
3003 goto error;
3004 }
3005 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3006
3007 if (!*(args[cur_arg+1])) {
3008 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3009 goto error;
3010 }
3011 cur_arg++;
3012 pattern = args[cur_arg];
3013 }
3014 else if (strcmp(args[cur_arg], "custom") == 0) {
3015 if (in_pattern) {
3016 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3017 goto error;
3018 }
3019 if (type != TCPCHK_EXPECT_UNDEF) {
3020 memprintf(errmsg, "only on pattern expected");
3021 goto error;
3022 }
3023 type = TCPCHK_EXPECT_CUSTOM;
3024 }
3025 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3026 int orig_arg = cur_arg;
3027
3028 if (proto != TCPCHK_RULES_HTTP_CHK)
3029 goto bad_tcp_kw;
3030 if (type != TCPCHK_EXPECT_UNDEF) {
3031 memprintf(errmsg, "only on pattern expected");
3032 goto error;
3033 }
3034 type = TCPCHK_EXPECT_HTTP_HEADER;
3035
3036 if (strcmp(args[cur_arg], "fhdr") == 0)
3037 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3038
3039 /* Parse the name pattern, mandatory */
3040 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3041 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3042 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3043 args[orig_arg]);
3044 goto error;
3045 }
3046
3047 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3048 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3049
3050 cur_arg += 2;
3051 if (strcmp(args[cur_arg], "-m") == 0) {
3052 if (!*(args[cur_arg+1])) {
3053 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3054 args[orig_arg], args[cur_arg]);
3055 goto error;
3056 }
3057 if (strcmp(args[cur_arg+1], "str") == 0)
3058 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3059 else if (strcmp(args[cur_arg+1], "beg") == 0)
3060 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3061 else if (strcmp(args[cur_arg+1], "end") == 0)
3062 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3063 else if (strcmp(args[cur_arg+1], "sub") == 0)
3064 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3065 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3066 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3067 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3068 args[orig_arg]);
3069 goto error;
3070 }
3071 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3072 }
3073 else {
3074 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3075 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3076 goto error;
3077 }
3078 cur_arg += 2;
3079 }
3080 else
3081 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3082 npat = args[cur_arg];
3083
3084 if (!*(args[cur_arg+1]) ||
3085 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3086 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3087 goto next;
3088 }
3089 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3090 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3091
3092 /* Parse the value pattern, optional */
3093 if (strcmp(args[cur_arg+2], "-m") == 0) {
3094 cur_arg += 2;
3095 if (!*(args[cur_arg+1])) {
3096 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3097 args[orig_arg], args[cur_arg]);
3098 goto error;
3099 }
3100 if (strcmp(args[cur_arg+1], "str") == 0)
3101 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3102 else if (strcmp(args[cur_arg+1], "beg") == 0)
3103 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3104 else if (strcmp(args[cur_arg+1], "end") == 0)
3105 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3106 else if (strcmp(args[cur_arg+1], "sub") == 0)
3107 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3108 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3109 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3110 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3111 args[orig_arg]);
3112 goto error;
3113 }
3114 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3115 }
3116 else {
3117 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3118 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3119 goto error;
3120 }
3121 }
3122 else
3123 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3124
3125 if (!*(args[cur_arg+2])) {
3126 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3127 goto error;
3128 }
3129 vpat = args[cur_arg+2];
3130 cur_arg += 2;
3131 }
3132 else if (strcmp(args[cur_arg], "comment") == 0) {
3133 if (in_pattern) {
3134 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3135 goto error;
3136 }
3137 if (!*(args[cur_arg+1])) {
3138 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3139 goto error;
3140 }
3141 cur_arg++;
3142 free(comment);
3143 comment = strdup(args[cur_arg]);
3144 if (!comment) {
3145 memprintf(errmsg, "out of memory");
3146 goto error;
3147 }
3148 }
3149 else if (strcmp(args[cur_arg], "on-success") == 0) {
3150 if (in_pattern) {
3151 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3152 goto error;
3153 }
3154 if (!*(args[cur_arg+1])) {
3155 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3156 goto error;
3157 }
3158 cur_arg++;
3159 on_success_msg = args[cur_arg];
3160 }
3161 else if (strcmp(args[cur_arg], "on-error") == 0) {
3162 if (in_pattern) {
3163 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3164 goto error;
3165 }
3166 if (!*(args[cur_arg+1])) {
3167 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3168 goto error;
3169 }
3170 cur_arg++;
3171 on_error_msg = args[cur_arg];
3172 }
3173 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3174 if (in_pattern) {
3175 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3176 goto error;
3177 }
3178 if (!*(args[cur_arg+1])) {
3179 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3180 goto error;
3181 }
3182 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3183 ok_st = HCHK_STATUS_L7OKD;
3184 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3185 ok_st = HCHK_STATUS_L7OKCD;
3186 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3187 ok_st = HCHK_STATUS_L6OK;
3188 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3189 ok_st = HCHK_STATUS_L4OK;
3190 else {
3191 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3192 args[cur_arg], args[cur_arg+1]);
3193 goto error;
3194 }
3195 cur_arg++;
3196 }
3197 else if (strcmp(args[cur_arg], "error-status") == 0) {
3198 if (in_pattern) {
3199 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3200 goto error;
3201 }
3202 if (!*(args[cur_arg+1])) {
3203 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3204 goto error;
3205 }
3206 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3207 err_st = HCHK_STATUS_L7RSP;
3208 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3209 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003210 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3211 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003212 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3213 err_st = HCHK_STATUS_L6RSP;
3214 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3215 err_st = HCHK_STATUS_L4CON;
3216 else {
3217 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3218 args[cur_arg], args[cur_arg+1]);
3219 goto error;
3220 }
3221 cur_arg++;
3222 }
3223 else if (strcmp(args[cur_arg], "status-code") == 0) {
3224 int idx = 0;
3225
3226 if (in_pattern) {
3227 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3228 goto error;
3229 }
3230 if (!*(args[cur_arg+1])) {
3231 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3232 goto error;
3233 }
3234
3235 cur_arg++;
3236 release_sample_expr(status_expr);
3237 px->conf.args.ctx = ARGC_SRV;
3238 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003239 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003240 if (!status_expr) {
3241 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3242 goto error;
3243 }
3244 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3245 memprintf(errmsg, "error detected while parsing status-code expression : "
3246 " fetch method '%s' extracts information from '%s', "
3247 "none of which is available here.\n",
3248 args[cur_arg], sample_src_names(status_expr->fetch->use));
3249 goto error;
3250 }
3251 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3252 }
3253 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3254 if (in_pattern) {
3255 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3256 goto error;
3257 }
3258 if (!*(args[cur_arg+1])) {
3259 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3260 goto error;
3261 }
3262 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3263 tout_st = HCHK_STATUS_L7TOUT;
3264 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3265 tout_st = HCHK_STATUS_L6TOUT;
3266 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3267 tout_st = HCHK_STATUS_L4TOUT;
3268 else {
3269 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3270 args[cur_arg], args[cur_arg+1]);
3271 goto error;
3272 }
3273 cur_arg++;
3274 }
3275 else {
3276 if (proto == TCPCHK_RULES_HTTP_CHK) {
3277 bad_http_kw:
3278 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3279 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3280 }
3281 else {
3282 bad_tcp_kw:
3283 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3284 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3285 }
3286 goto error;
3287 }
3288 next:
3289 cur_arg++;
3290 }
3291
3292 chk = calloc(1, sizeof(*chk));
3293 if (!chk) {
3294 memprintf(errmsg, "out of memory");
3295 goto error;
3296 }
3297 chk->action = TCPCHK_ACT_EXPECT;
3298 LIST_INIT(&chk->expect.onerror_fmt);
3299 LIST_INIT(&chk->expect.onsuccess_fmt);
3300 chk->comment = comment; comment = NULL;
3301 chk->expect.type = type;
3302 chk->expect.min_recv = min_recv;
3303 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3304 chk->expect.ok_status = ok_st;
3305 chk->expect.err_status = err_st;
3306 chk->expect.tout_status = tout_st;
3307 chk->expect.status_expr = status_expr; status_expr = NULL;
3308
3309 if (on_success_msg) {
3310 px->conf.args.ctx = ARGC_SRV;
3311 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3312 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3313 goto error;
3314 }
3315 }
3316 if (on_error_msg) {
3317 px->conf.args.ctx = ARGC_SRV;
3318 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3319 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3320 goto error;
3321 }
3322 }
3323
3324 switch (chk->expect.type) {
3325 case TCPCHK_EXPECT_HTTP_STATUS: {
3326 const char *p = pattern;
3327 unsigned int c1,c2;
3328
3329 chk->expect.codes.codes = NULL;
3330 chk->expect.codes.num = 0;
3331 while (1) {
3332 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3333 if (*p == '-') {
3334 p++;
3335 c2 = read_uint(&p, pattern + strlen(pattern));
3336 }
3337 if (c1 > c2) {
3338 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3339 goto error;
3340 }
3341
3342 chk->expect.codes.num++;
3343 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3344 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3345 if (!chk->expect.codes.codes) {
3346 memprintf(errmsg, "out of memory");
3347 goto error;
3348 }
3349 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3350 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3351
3352 if (*p == '\0')
3353 break;
3354 if (*p != ',') {
3355 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3356 goto error;
3357 }
3358 p++;
3359 }
3360 break;
3361 }
3362 case TCPCHK_EXPECT_STRING:
3363 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003364 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003365 if (!isttest(chk->expect.data)) {
3366 memprintf(errmsg, "out of memory");
3367 goto error;
3368 }
3369 break;
3370 case TCPCHK_EXPECT_BINARY: {
3371 int len = chk->expect.data.len;
3372
3373 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3374 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3375 goto error;
3376 }
3377 chk->expect.data.len = len;
3378 break;
3379 }
3380 case TCPCHK_EXPECT_STRING_REGEX:
3381 case TCPCHK_EXPECT_BINARY_REGEX:
3382 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3383 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3384 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3385 if (!chk->expect.regex)
3386 goto error;
3387 break;
3388
3389 case TCPCHK_EXPECT_STRING_LF:
3390 case TCPCHK_EXPECT_BINARY_LF:
3391 case TCPCHK_EXPECT_HTTP_BODY_LF:
3392 LIST_INIT(&chk->expect.fmt);
3393 px->conf.args.ctx = ARGC_SRV;
3394 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3395 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3396 goto error;
3397 }
3398 break;
3399
3400 case TCPCHK_EXPECT_HTTP_HEADER:
3401 if (!npat) {
3402 memprintf(errmsg, "unexpected error, undefined header name pattern");
3403 goto error;
3404 }
3405 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3406 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3407 if (!chk->expect.hdr.name_re)
3408 goto error;
3409 }
3410 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3411 px->conf.args.ctx = ARGC_SRV;
3412 LIST_INIT(&chk->expect.hdr.name_fmt);
3413 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3414 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3415 goto error;
3416 }
3417 }
3418 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003419 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003420 if (!isttest(chk->expect.hdr.name)) {
3421 memprintf(errmsg, "out of memory");
3422 goto error;
3423 }
3424 }
3425
3426 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3427 chk->expect.hdr.value = IST_NULL;
3428 break;
3429 }
3430
3431 if (!vpat) {
3432 memprintf(errmsg, "unexpected error, undefined header value pattern");
3433 goto error;
3434 }
3435 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3436 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3437 if (!chk->expect.hdr.value_re)
3438 goto error;
3439 }
3440 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3441 px->conf.args.ctx = ARGC_SRV;
3442 LIST_INIT(&chk->expect.hdr.value_fmt);
3443 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3444 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3445 goto error;
3446 }
3447 }
3448 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003449 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003450 if (!isttest(chk->expect.hdr.value)) {
3451 memprintf(errmsg, "out of memory");
3452 goto error;
3453 }
3454 }
3455
3456 break;
3457 case TCPCHK_EXPECT_CUSTOM:
3458 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3459 break;
3460 case TCPCHK_EXPECT_UNDEF:
3461 memprintf(errmsg, "pattern not found");
3462 goto error;
3463 }
3464
3465 /* All tcp-check expect points back to the first inverse expect rule in
3466 * a chain of one or more expect rule, potentially itself.
3467 */
3468 chk->expect.head = chk;
3469 list_for_each_entry_rev(prev_check, rules, list) {
3470 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3471 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3472 chk->expect.head = prev_check;
3473 continue;
3474 }
3475 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3476 break;
3477 }
3478 return chk;
3479
3480 error:
3481 free_tcpcheck(chk, 0);
3482 free(comment);
3483 release_sample_expr(status_expr);
3484 return NULL;
3485}
3486
3487/* Overwrites fields of the old http send rule with those of the new one. When
3488 * replaced, old values are freed and replaced by the new ones. New values are
3489 * not copied but transferred. At the end <new> should be empty and can be
3490 * safely released. This function never fails.
3491 */
3492void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3493{
3494 struct logformat_node *lf, *lfb;
3495 struct tcpcheck_http_hdr *hdr, *bhdr;
3496
3497
3498 if (new->send.http.meth.str.area) {
3499 free(old->send.http.meth.str.area);
3500 old->send.http.meth.meth = new->send.http.meth.meth;
3501 old->send.http.meth.str.area = new->send.http.meth.str.area;
3502 old->send.http.meth.str.data = new->send.http.meth.str.data;
3503 new->send.http.meth.str = BUF_NULL;
3504 }
3505
3506 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3507 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3508 istfree(&old->send.http.uri);
3509 else
3510 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3511 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3512 old->send.http.uri = new->send.http.uri;
3513 new->send.http.uri = IST_NULL;
3514 }
3515 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3516 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3517 istfree(&old->send.http.uri);
3518 else
3519 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3520 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3521 LIST_INIT(&old->send.http.uri_fmt);
3522 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003523 LIST_DELETE(&lf->list);
3524 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003525 }
3526 }
3527
3528 if (isttest(new->send.http.vsn)) {
3529 istfree(&old->send.http.vsn);
3530 old->send.http.vsn = new->send.http.vsn;
3531 new->send.http.vsn = IST_NULL;
3532 }
3533
3534 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3535 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003536 LIST_DELETE(&hdr->list);
3537 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003538 }
3539
3540 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3541 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3542 istfree(&old->send.http.body);
3543 else
3544 free_tcpcheck_fmt(&old->send.http.body_fmt);
3545 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3546 old->send.http.body = new->send.http.body;
3547 new->send.http.body = IST_NULL;
3548 }
3549 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3550 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3551 istfree(&old->send.http.body);
3552 else
3553 free_tcpcheck_fmt(&old->send.http.body_fmt);
3554 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3555 LIST_INIT(&old->send.http.body_fmt);
3556 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003557 LIST_DELETE(&lf->list);
3558 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003559 }
3560 }
3561}
3562
3563/* Internal function used to add an http-check rule in a list during the config
3564 * parsing step. Depending on its type, and the previously inserted rules, a
3565 * specific action may be performed or an error may be reported. This functions
3566 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3567 * message.
3568 */
3569int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3570{
3571 struct tcpcheck_rule *r;
3572
3573 /* the implicit send rule coming from an "option httpchk" line must be
3574 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003575 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003576 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003577 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003578 * sure the ruleset remains valid.
3579 */
3580
3581 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3582 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3583 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3584 * following tests are performed :
3585 *
3586 * 1- If there is no such rule or if it is not a send rule, the implicit send
3587 * rule is pushed in front of the ruleset
3588 *
3589 * 2- If it is another implicit send rule, it is replaced with the new one.
3590 *
3591 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3592 * both, overwriting the old send rule (the explicit one) with info of the
3593 * new send rule (the implicit one).
3594 */
3595 r = get_first_tcpcheck_rule(rules);
3596 if (r && r->action == TCPCHK_ACT_CONNECT)
3597 r = get_next_tcpcheck_rule(rules, r);
3598 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003599 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003600 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003601 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003602 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003603 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003604 }
3605 else {
3606 tcpcheck_overwrite_send_http_rule(r, chk);
3607 free_tcpcheck(chk, 0);
3608 }
3609 }
3610 else {
3611 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3612 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3613 * with an existing implicit send rule, if any. At the end, if there is no error,
3614 * the rule is appended to the list.
3615 */
3616
3617 r = get_last_tcpcheck_rule(rules);
3618 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3619 /* no error */;
3620 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3621 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3622 chk->index+1);
3623 return 0;
3624 }
3625 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3626 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3627 chk->index+1);
3628 return 0;
3629 }
3630 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3631 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3632 chk->index+1);
3633 return 0;
3634 }
3635
3636 if (chk->action == TCPCHK_ACT_SEND) {
3637 r = get_first_tcpcheck_rule(rules);
3638 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3639 tcpcheck_overwrite_send_http_rule(r, chk);
3640 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003641 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003642 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3643 chk = r;
3644 }
3645 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003646 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003647 }
3648 return 1;
3649}
3650
3651/* Check tcp-check health-check configuration for the proxy <px>. */
3652static int check_proxy_tcpcheck(struct proxy *px)
3653{
3654 struct tcpcheck_rule *chk, *back;
3655 char *comment = NULL, *errmsg = NULL;
3656 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003657 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003658
3659 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3660 deinit_proxy_tcpcheck(px);
3661 goto out;
3662 }
3663
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003664 ha_free(&px->check_command);
3665 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003666
3667 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003668 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003669 ret |= ERR_ALERT | ERR_FATAL;
3670 goto out;
3671 }
3672
3673 /* HTTP ruleset only : */
3674 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3675 struct tcpcheck_rule *next;
3676
3677 /* move remaining implicit send rule from "option httpchk" line to the right place.
3678 * If such rule exists, it must be the first one. In this case, the rule is moved
3679 * after the first connect rule, if any. Otherwise, nothing is done.
3680 */
3681 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3682 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3683 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3684 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003685 LIST_DELETE(&chk->list);
3686 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003687 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003688 }
3689 }
3690
3691 /* add implicit expect rule if the last one is a send. It is inherited from previous
3692 * versions where the http expect rule was optional. Now it is possible to chained
3693 * send/expect rules but the last expect may still be implicit.
3694 */
3695 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3696 if (chk && chk->action == TCPCHK_ACT_SEND) {
3697 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3698 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3699 px->conf.file, px->conf.line, &errmsg);
3700 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003701 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003702 "(%s).\n", px->id, errmsg);
3703 free(errmsg);
3704 ret |= ERR_ALERT | ERR_FATAL;
3705 goto out;
3706 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003707 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003708 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003709 }
3710 }
3711
3712 /* For all ruleset: */
3713
3714 /* If there is no connect rule preceding all send / expect rules, an
3715 * implicit one is inserted before all others.
3716 */
3717 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3718 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3719 chk = calloc(1, sizeof(*chk));
3720 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003721 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003722 "(out of memory).\n", px->id);
3723 ret |= ERR_ALERT | ERR_FATAL;
3724 goto out;
3725 }
3726 chk->action = TCPCHK_ACT_CONNECT;
3727 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003728 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003729 }
3730
3731 /* Remove all comment rules. To do so, when a such rule is found, the
3732 * comment is assigned to the following rule(s).
3733 */
3734 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003735 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3736 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003737
3738 prev_action = chk->action;
3739 switch (chk->action) {
3740 case TCPCHK_ACT_COMMENT:
3741 free(comment);
3742 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003743 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003744 free(chk);
3745 break;
3746 case TCPCHK_ACT_CONNECT:
3747 if (!chk->comment && comment)
3748 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003749 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003750 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003751 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003752 break;
3753 case TCPCHK_ACT_SEND:
3754 case TCPCHK_ACT_EXPECT:
3755 if (!chk->comment && comment)
3756 chk->comment = strdup(comment);
3757 break;
3758 }
3759 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003760 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003761
3762 out:
3763 return ret;
3764}
3765
3766void deinit_proxy_tcpcheck(struct proxy *px)
3767{
3768 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3769 px->tcpcheck_rules.flags = 0;
3770 px->tcpcheck_rules.list = NULL;
3771}
3772
3773static void deinit_tcpchecks()
3774{
3775 struct tcpcheck_ruleset *rs;
3776 struct tcpcheck_rule *r, *rb;
3777 struct ebpt_node *node, *next;
3778
3779 node = ebpt_first(&shared_tcpchecks);
3780 while (node) {
3781 next = ebpt_next(node);
3782 ebpt_delete(node);
3783 free(node->key);
3784 rs = container_of(node, typeof(*rs), node);
3785 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003786 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003787 free_tcpcheck(r, 0);
3788 }
3789 free(rs);
3790 node = next;
3791 }
3792}
3793
3794int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3795{
3796 struct tcpcheck_rule *tcpcheck, *prev_check;
3797 struct tcpcheck_expect *expect;
3798
Willy Tarreau6922e552021-03-22 21:11:45 +01003799 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003800 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003801 tcpcheck->action = TCPCHK_ACT_EXPECT;
3802
3803 expect = &tcpcheck->expect;
3804 expect->type = TCPCHK_EXPECT_STRING;
3805 LIST_INIT(&expect->onerror_fmt);
3806 LIST_INIT(&expect->onsuccess_fmt);
3807 expect->ok_status = HCHK_STATUS_L7OKD;
3808 expect->err_status = HCHK_STATUS_L7RSP;
3809 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003810 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003811 if (!isttest(expect->data)) {
3812 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3813 return 0;
3814 }
3815
3816 /* All tcp-check expect points back to the first inverse expect rule
3817 * in a chain of one or more expect rule, potentially itself.
3818 */
3819 tcpcheck->expect.head = tcpcheck;
3820 list_for_each_entry_rev(prev_check, rules->list, list) {
3821 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3822 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3823 tcpcheck->expect.head = prev_check;
3824 continue;
3825 }
3826 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3827 break;
3828 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003829 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003830 return 1;
3831}
3832
3833int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3834{
3835 struct tcpcheck_rule *tcpcheck;
3836 struct tcpcheck_send *send;
3837 const char *in;
3838 char *dst;
3839 int i;
3840
Willy Tarreau6922e552021-03-22 21:11:45 +01003841 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003842 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003843 tcpcheck->action = TCPCHK_ACT_SEND;
3844
3845 send = &tcpcheck->send;
3846 send->type = TCPCHK_SEND_STRING;
3847
3848 for (i = 0; strs[i]; i++)
3849 send->data.len += strlen(strs[i]);
3850
3851 send->data.ptr = malloc(istlen(send->data) + 1);
3852 if (!isttest(send->data)) {
3853 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3854 return 0;
3855 }
3856
3857 dst = istptr(send->data);
3858 for (i = 0; strs[i]; i++)
3859 for (in = strs[i]; (*dst = *in++); dst++);
3860 *dst = 0;
3861
Willy Tarreau2b718102021-04-21 07:32:39 +02003862 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003863 return 1;
3864}
3865
3866/* Parses the "tcp-check" proxy keyword */
3867static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003868 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003869 char **errmsg)
3870{
3871 struct tcpcheck_ruleset *rs = NULL;
3872 struct tcpcheck_rule *chk = NULL;
3873 int index, cur_arg, ret = 0;
3874
3875 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3876 ret = 1;
3877
3878 /* Deduce the ruleset name from the proxy info */
3879 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3880 ((curpx == defpx) ? "defaults" : curpx->id),
3881 curpx->conf.file, curpx->conf.line);
3882
3883 rs = find_tcpcheck_ruleset(b_orig(&trash));
3884 if (rs == NULL) {
3885 rs = create_tcpcheck_ruleset(b_orig(&trash));
3886 if (rs == NULL) {
3887 memprintf(errmsg, "out of memory.\n");
3888 goto error;
3889 }
3890 }
3891
3892 index = 0;
3893 if (!LIST_ISEMPTY(&rs->rules)) {
3894 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3895 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003896 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003897 }
3898
3899 cur_arg = 1;
3900 if (strcmp(args[cur_arg], "connect") == 0)
3901 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3902 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3903 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3904 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3905 else if (strcmp(args[cur_arg], "expect") == 0)
3906 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3907 else if (strcmp(args[cur_arg], "comment") == 0)
3908 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3909 else {
3910 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3911
3912 if (!kw) {
3913 action_kw_tcp_check_build_list(&trash);
3914 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3915 "%s%s. but got '%s'",
3916 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3917 goto error;
3918 }
3919 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3920 }
3921
3922 if (!chk) {
3923 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3924 goto error;
3925 }
3926 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3927
3928 /* No error: add the tcp-check rule in the list */
3929 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003930 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003931
3932 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3933 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3934 /* Use this ruleset if the proxy already has tcp-check enabled */
3935 curpx->tcpcheck_rules.list = &rs->rules;
3936 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3937 }
3938 else {
3939 /* mark this ruleset as unused for now */
3940 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3941 }
3942
3943 return ret;
3944
3945 error:
3946 free_tcpcheck(chk, 0);
3947 free_tcpcheck_ruleset(rs);
3948 return -1;
3949}
3950
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003951/* Parses the "http-check" proxy keyword */
3952static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003953 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003954 char **errmsg)
3955{
3956 struct tcpcheck_ruleset *rs = NULL;
3957 struct tcpcheck_rule *chk = NULL;
3958 int index, cur_arg, ret = 0;
3959
3960 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3961 ret = 1;
3962
3963 cur_arg = 1;
3964 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3965 /* enable a graceful server shutdown on an HTTP 404 response */
3966 curpx->options |= PR_O_DISABLE404;
3967 if (too_many_args(1, args, errmsg, NULL))
3968 goto error;
3969 goto out;
3970 }
3971 else if (strcmp(args[cur_arg], "send-state") == 0) {
3972 /* enable emission of the apparent state of a server in HTTP checks */
3973 curpx->options2 |= PR_O2_CHK_SNDST;
3974 if (too_many_args(1, args, errmsg, NULL))
3975 goto error;
3976 goto out;
3977 }
3978
3979 /* Deduce the ruleset name from the proxy info */
3980 chunk_printf(&trash, "*http-check-%s_%s-%d",
3981 ((curpx == defpx) ? "defaults" : curpx->id),
3982 curpx->conf.file, curpx->conf.line);
3983
3984 rs = find_tcpcheck_ruleset(b_orig(&trash));
3985 if (rs == NULL) {
3986 rs = create_tcpcheck_ruleset(b_orig(&trash));
3987 if (rs == NULL) {
3988 memprintf(errmsg, "out of memory.\n");
3989 goto error;
3990 }
3991 }
3992
3993 index = 0;
3994 if (!LIST_ISEMPTY(&rs->rules)) {
3995 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3996 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
3997 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003998 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003999 }
4000
4001 if (strcmp(args[cur_arg], "connect") == 0)
4002 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4003 else if (strcmp(args[cur_arg], "send") == 0)
4004 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4005 else if (strcmp(args[cur_arg], "expect") == 0)
4006 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4007 file, line, errmsg);
4008 else if (strcmp(args[cur_arg], "comment") == 0)
4009 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4010 else {
4011 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4012
4013 if (!kw) {
4014 action_kw_tcp_check_build_list(&trash);
4015 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4016 " 'send', 'expect'%s%s. but got '%s'",
4017 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4018 goto error;
4019 }
4020 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4021 }
4022
4023 if (!chk) {
4024 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4025 goto error;
4026 }
4027 ret = (*errmsg != NULL); /* Handle warning */
4028
4029 chk->index = index;
4030 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4031 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4032 /* Use this ruleset if the proxy already has http-check enabled */
4033 curpx->tcpcheck_rules.list = &rs->rules;
4034 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4035 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4036 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4037 curpx->tcpcheck_rules.list = NULL;
4038 goto error;
4039 }
4040 }
4041 else {
4042 /* mark this ruleset as unused for now */
4043 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004044 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004045 }
4046
4047 out:
4048 return ret;
4049
4050 error:
4051 free_tcpcheck(chk, 0);
4052 free_tcpcheck_ruleset(rs);
4053 return -1;
4054}
4055
4056/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004057int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004058 const char *file, int line)
4059{
4060 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4061 static char *redis_res = "+PONG\r\n";
4062
4063 struct tcpcheck_ruleset *rs = NULL;
4064 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4065 struct tcpcheck_rule *chk;
4066 char *errmsg = NULL;
4067 int err_code = 0;
4068
4069 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4070 err_code |= ERR_WARN;
4071
4072 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4073 goto out;
4074
4075 curpx->options2 &= ~PR_O2_CHK_ANY;
4076 curpx->options2 |= PR_O2_TCPCHK_CHK;
4077
4078 free_tcpcheck_vars(&rules->preset_vars);
4079 rules->list = NULL;
4080 rules->flags = 0;
4081
4082 rs = find_tcpcheck_ruleset("*redis-check");
4083 if (rs)
4084 goto ruleset_found;
4085
4086 rs = create_tcpcheck_ruleset("*redis-check");
4087 if (rs == NULL) {
4088 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4089 goto error;
4090 }
4091
4092 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4093 1, curpx, &rs->rules, file, line, &errmsg);
4094 if (!chk) {
4095 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4096 goto error;
4097 }
4098 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004099 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004100
4101 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4102 "error-status", "L7STS",
4103 "on-error", "%[res.payload(0,0),cut_crlf]",
4104 "on-success", "Redis server is ok",
4105 ""},
4106 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4107 if (!chk) {
4108 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4109 goto error;
4110 }
4111 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004112 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004113
4114 ruleset_found:
4115 rules->list = &rs->rules;
4116 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4117 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4118
4119 out:
4120 free(errmsg);
4121 return err_code;
4122
4123 error:
4124 free_tcpcheck_ruleset(rs);
4125 err_code |= ERR_ALERT | ERR_FATAL;
4126 goto out;
4127}
4128
4129
4130/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004131int 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 +01004132 const char *file, int line)
4133{
4134 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4135 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4136 *
4137 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4138 */
4139 static char sslv3_client_hello[] = {
4140 "16" /* ContentType : 0x16 = Handshake */
4141 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4142 "0079" /* ContentLength : 0x79 bytes after this one */
4143 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4144 "000075" /* HandshakeLength : 0x75 bytes after this one */
4145 "0300" /* Hello Version : 0x0300 = v3 */
4146 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4147 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4148 "00" /* Session ID length : empty (no session ID) */
4149 "004E" /* Cipher Suite Length : 78 bytes after this one */
4150 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4151 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4152 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4153 "000D" "000E" "000F" "0010" /* various bit lengths, */
4154 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4155 "0015" "0016" "0017" "0018"
4156 "0019" "001A" "001B" "002F"
4157 "0030" "0031" "0032" "0033"
4158 "0034" "0035" "0036" "0037"
4159 "0038" "0039" "003A"
4160 "01" /* Compression Length : 0x01 = 1 byte for types */
4161 "00" /* Compression Type : 0x00 = NULL compression */
4162 };
4163
4164 struct tcpcheck_ruleset *rs = NULL;
4165 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4166 struct tcpcheck_rule *chk;
4167 char *errmsg = NULL;
4168 int err_code = 0;
4169
4170 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4171 err_code |= ERR_WARN;
4172
4173 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4174 goto out;
4175
4176 curpx->options2 &= ~PR_O2_CHK_ANY;
4177 curpx->options2 |= PR_O2_TCPCHK_CHK;
4178
4179 free_tcpcheck_vars(&rules->preset_vars);
4180 rules->list = NULL;
4181 rules->flags = 0;
4182
4183 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4184 if (rs)
4185 goto ruleset_found;
4186
4187 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4188 if (rs == NULL) {
4189 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4190 goto error;
4191 }
4192
4193 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4194 1, curpx, &rs->rules, file, line, &errmsg);
4195 if (!chk) {
4196 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4197 goto error;
4198 }
4199 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004200 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004201
4202 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4203 "min-recv", "5", "ok-status", "L6OK",
4204 "error-status", "L6RSP", "tout-status", "L6TOUT",
4205 ""},
4206 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4207 if (!chk) {
4208 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4209 goto error;
4210 }
4211 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004212 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004213
4214 ruleset_found:
4215 rules->list = &rs->rules;
4216 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4217 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4218
4219 out:
4220 free(errmsg);
4221 return err_code;
4222
4223 error:
4224 free_tcpcheck_ruleset(rs);
4225 err_code |= ERR_ALERT | ERR_FATAL;
4226 goto out;
4227}
4228
4229/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004230int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004231 const char *file, int line)
4232{
4233 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4234
4235 struct tcpcheck_ruleset *rs = NULL;
4236 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4237 struct tcpcheck_rule *chk;
4238 struct tcpcheck_var *var = NULL;
4239 char *cmd = NULL, *errmsg = NULL;
4240 int err_code = 0;
4241
4242 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4243 err_code |= ERR_WARN;
4244
4245 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4246 goto out;
4247
4248 curpx->options2 &= ~PR_O2_CHK_ANY;
4249 curpx->options2 |= PR_O2_TCPCHK_CHK;
4250
4251 free_tcpcheck_vars(&rules->preset_vars);
4252 rules->list = NULL;
4253 rules->flags = 0;
4254
4255 cur_arg += 2;
4256 if (*args[cur_arg] && *args[cur_arg+1] &&
4257 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4258 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4259 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4260 if (cmd)
4261 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4262 }
4263 else {
4264 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4265 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4266 cmd = strdup("HELO localhost");
4267 }
4268
4269 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4270 if (cmd == NULL || var == NULL) {
4271 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4272 goto error;
4273 }
4274 var->data.type = SMP_T_STR;
4275 var->data.u.str.area = cmd;
4276 var->data.u.str.data = strlen(cmd);
4277 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004278 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004279 cmd = NULL;
4280 var = NULL;
4281
4282 rs = find_tcpcheck_ruleset("*smtp-check");
4283 if (rs)
4284 goto ruleset_found;
4285
4286 rs = create_tcpcheck_ruleset("*smtp-check");
4287 if (rs == NULL) {
4288 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4289 goto error;
4290 }
4291
4292 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4293 1, curpx, &rs->rules, file, line, &errmsg);
4294 if (!chk) {
4295 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4296 goto error;
4297 }
4298 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004299 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004300
4301 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4302 "min-recv", "4",
4303 "error-status", "L7RSP",
4304 "on-error", "%[res.payload(0,0),cut_crlf]",
4305 ""},
4306 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4307 if (!chk) {
4308 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4309 goto error;
4310 }
4311 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004312 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004313
4314 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4315 "min-recv", "4",
4316 "error-status", "L7STS",
4317 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4318 "status-code", "res.payload(0,3)",
4319 ""},
4320 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4321 if (!chk) {
4322 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4323 goto error;
4324 }
4325 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004326 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004327
4328 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4329 1, curpx, &rs->rules, file, line, &errmsg);
4330 if (!chk) {
4331 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4332 goto error;
4333 }
4334 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004335 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004336
4337 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4338 "min-recv", "4",
4339 "error-status", "L7STS",
4340 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4341 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4342 "status-code", "res.payload(0,3)",
4343 ""},
4344 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4345 if (!chk) {
4346 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4347 goto error;
4348 }
4349 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004350 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004351
4352 ruleset_found:
4353 rules->list = &rs->rules;
4354 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4355 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4356
4357 out:
4358 free(errmsg);
4359 return err_code;
4360
4361 error:
4362 free(cmd);
4363 free(var);
4364 free_tcpcheck_vars(&rules->preset_vars);
4365 free_tcpcheck_ruleset(rs);
4366 err_code |= ERR_ALERT | ERR_FATAL;
4367 goto out;
4368}
4369
4370/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004371int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004372 const char *file, int line)
4373{
4374 static char pgsql_req[] = {
4375 "%[var(check.plen),htonl,hex]" /* The packet length*/
4376 "00030000" /* the version 3.0 */
4377 "7573657200" /* "user" key */
4378 "%[var(check.username),hex]00" /* the username */
4379 "00"
4380 };
4381
4382 struct tcpcheck_ruleset *rs = NULL;
4383 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4384 struct tcpcheck_rule *chk;
4385 struct tcpcheck_var *var = NULL;
4386 char *user = NULL, *errmsg = NULL;
4387 size_t packetlen = 0;
4388 int err_code = 0;
4389
4390 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4391 err_code |= ERR_WARN;
4392
4393 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4394 goto out;
4395
4396 curpx->options2 &= ~PR_O2_CHK_ANY;
4397 curpx->options2 |= PR_O2_TCPCHK_CHK;
4398
4399 free_tcpcheck_vars(&rules->preset_vars);
4400 rules->list = NULL;
4401 rules->flags = 0;
4402
4403 cur_arg += 2;
4404 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4405 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4406 file, line, args[0], args[1]);
4407 goto error;
4408 }
4409 if (strcmp(args[cur_arg], "user") == 0) {
4410 packetlen = 15 + strlen(args[cur_arg+1]);
4411 user = strdup(args[cur_arg+1]);
4412
4413 var = create_tcpcheck_var(ist("check.username"));
4414 if (user == NULL || var == NULL) {
4415 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4416 goto error;
4417 }
4418 var->data.type = SMP_T_STR;
4419 var->data.u.str.area = user;
4420 var->data.u.str.data = strlen(user);
4421 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004422 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004423 user = NULL;
4424 var = NULL;
4425
4426 var = create_tcpcheck_var(ist("check.plen"));
4427 if (var == NULL) {
4428 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4429 goto error;
4430 }
4431 var->data.type = SMP_T_SINT;
4432 var->data.u.sint = packetlen;
4433 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004434 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004435 var = NULL;
4436 }
4437 else {
4438 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4439 file, line, args[0], args[1]);
4440 goto error;
4441 }
4442
4443 rs = find_tcpcheck_ruleset("*pgsql-check");
4444 if (rs)
4445 goto ruleset_found;
4446
4447 rs = create_tcpcheck_ruleset("*pgsql-check");
4448 if (rs == NULL) {
4449 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4450 goto error;
4451 }
4452
4453 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4454 1, curpx, &rs->rules, file, line, &errmsg);
4455 if (!chk) {
4456 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4457 goto error;
4458 }
4459 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004460 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004461
4462 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4463 1, curpx, &rs->rules, file, line, &errmsg);
4464 if (!chk) {
4465 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4466 goto error;
4467 }
4468 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004469 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004470
4471 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4472 "min-recv", "5",
4473 "error-status", "L7RSP",
4474 "on-error", "%[res.payload(6,0)]",
4475 ""},
4476 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4477 if (!chk) {
4478 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4479 goto error;
4480 }
4481 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004482 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004483
4484 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4485 "min-recv", "9",
4486 "error-status", "L7STS",
4487 "on-success", "PostgreSQL server is ok",
4488 "on-error", "PostgreSQL unknown error",
4489 ""},
4490 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4491 if (!chk) {
4492 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4493 goto error;
4494 }
4495 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004496 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004497
4498 ruleset_found:
4499 rules->list = &rs->rules;
4500 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4501 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4502
4503 out:
4504 free(errmsg);
4505 return err_code;
4506
4507 error:
4508 free(user);
4509 free(var);
4510 free_tcpcheck_vars(&rules->preset_vars);
4511 free_tcpcheck_ruleset(rs);
4512 err_code |= ERR_ALERT | ERR_FATAL;
4513 goto out;
4514}
4515
4516
4517/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004518int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004519 const char *file, int line)
4520{
4521 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4522 * const char mysql40_client_auth_pkt[] = {
4523 * "\x0e\x00\x00" // packet length
4524 * "\x01" // packet number
4525 * "\x00\x00" // client capabilities
4526 * "\x00\x00\x01" // max packet
4527 * "haproxy\x00" // username (null terminated string)
4528 * "\x00" // filler (always 0x00)
4529 * "\x01\x00\x00" // packet length
4530 * "\x00" // packet number
4531 * "\x01" // COM_QUIT command
4532 * };
4533 */
4534 static char mysql40_rsname[] = "*mysql40-check";
4535 static char mysql40_req[] = {
4536 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4537 "0080" /* client capabilities */
4538 "000001" /* max packet */
4539 "%[var(check.username),hex]00" /* the username */
4540 "00" /* filler (always 0x00) */
4541 "010000" /* packet length*/
4542 "00" /* sequence ID */
4543 "01" /* COM_QUIT command */
4544 };
4545
4546 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4547 * const char mysql41_client_auth_pkt[] = {
4548 * "\x0e\x00\x00\" // packet length
4549 * "\x01" // packet number
4550 * "\x00\x00\x00\x00" // client capabilities
4551 * "\x00\x00\x00\x01" // max packet
4552 * "\x21" // character set (UTF-8)
4553 * char[23] // All zeroes
4554 * "haproxy\x00" // username (null terminated string)
4555 * "\x00" // filler (always 0x00)
4556 * "\x01\x00\x00" // packet length
4557 * "\x00" // packet number
4558 * "\x01" // COM_QUIT command
4559 * };
4560 */
4561 static char mysql41_rsname[] = "*mysql41-check";
4562 static char mysql41_req[] = {
4563 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4564 "00820000" /* client capabilities */
4565 "00800001" /* max packet */
4566 "21" /* character set (UTF-8) */
4567 "000000000000000000000000" /* 23 bytes, al zeroes */
4568 "0000000000000000000000"
4569 "%[var(check.username),hex]00" /* the username */
4570 "00" /* filler (always 0x00) */
4571 "010000" /* packet length*/
4572 "00" /* sequence ID */
4573 "01" /* COM_QUIT command */
4574 };
4575
4576 struct tcpcheck_ruleset *rs = NULL;
4577 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4578 struct tcpcheck_rule *chk;
4579 struct tcpcheck_var *var = NULL;
4580 char *mysql_rsname = "*mysql-check";
4581 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4582 int index = 0, err_code = 0;
4583
4584 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4585 err_code |= ERR_WARN;
4586
4587 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4588 goto out;
4589
4590 curpx->options2 &= ~PR_O2_CHK_ANY;
4591 curpx->options2 |= PR_O2_TCPCHK_CHK;
4592
4593 free_tcpcheck_vars(&rules->preset_vars);
4594 rules->list = NULL;
4595 rules->flags = 0;
4596
4597 cur_arg += 2;
4598 if (*args[cur_arg]) {
4599 int packetlen, userlen;
4600
4601 if (strcmp(args[cur_arg], "user") != 0) {
4602 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4603 file, line, args[0], args[1], args[cur_arg]);
4604 goto error;
4605 }
4606
4607 if (*(args[cur_arg+1]) == 0) {
4608 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4609 file, line, args[0], args[1], args[cur_arg]);
4610 goto error;
4611 }
4612
4613 hdr = calloc(4, sizeof(*hdr));
4614 user = strdup(args[cur_arg+1]);
4615 userlen = strlen(args[cur_arg+1]);
4616
4617 if (hdr == NULL || user == NULL) {
4618 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4619 goto error;
4620 }
4621
4622 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4623 packetlen = userlen + 7 + 27;
4624 mysql_req = mysql41_req;
4625 mysql_rsname = mysql41_rsname;
4626 }
4627 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4628 packetlen = userlen + 7;
4629 mysql_req = mysql40_req;
4630 mysql_rsname = mysql40_rsname;
4631 }
4632 else {
4633 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4634 file, line, args[cur_arg], args[cur_arg+2]);
4635 goto error;
4636 }
4637
4638 hdr[0] = (unsigned char)(packetlen & 0xff);
4639 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4640 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4641 hdr[3] = 1;
4642
4643 var = create_tcpcheck_var(ist("check.header"));
4644 if (var == NULL) {
4645 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4646 goto error;
4647 }
4648 var->data.type = SMP_T_STR;
4649 var->data.u.str.area = hdr;
4650 var->data.u.str.data = 4;
4651 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004652 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004653 hdr = NULL;
4654 var = NULL;
4655
4656 var = create_tcpcheck_var(ist("check.username"));
4657 if (var == NULL) {
4658 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4659 goto error;
4660 }
4661 var->data.type = SMP_T_STR;
4662 var->data.u.str.area = user;
4663 var->data.u.str.data = strlen(user);
4664 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004665 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004666 user = NULL;
4667 var = NULL;
4668 }
4669
4670 rs = find_tcpcheck_ruleset(mysql_rsname);
4671 if (rs)
4672 goto ruleset_found;
4673
4674 rs = create_tcpcheck_ruleset(mysql_rsname);
4675 if (rs == NULL) {
4676 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4677 goto error;
4678 }
4679
4680 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4681 1, curpx, &rs->rules, file, line, &errmsg);
4682 if (!chk) {
4683 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4684 goto error;
4685 }
4686 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004687 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004688
4689 if (mysql_req) {
4690 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4691 1, curpx, &rs->rules, file, line, &errmsg);
4692 if (!chk) {
4693 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4694 goto error;
4695 }
4696 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004697 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004698 }
4699
4700 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4701 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4702 if (!chk) {
4703 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4704 goto error;
4705 }
4706 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4707 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004708 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004709
4710 if (mysql_req) {
4711 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4712 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4713 if (!chk) {
4714 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4715 goto error;
4716 }
4717 chk->expect.custom = tcpcheck_mysql_expect_ok;
4718 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004719 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004720 }
4721
4722 ruleset_found:
4723 rules->list = &rs->rules;
4724 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4725 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4726
4727 out:
4728 free(errmsg);
4729 return err_code;
4730
4731 error:
4732 free(hdr);
4733 free(user);
4734 free(var);
4735 free_tcpcheck_vars(&rules->preset_vars);
4736 free_tcpcheck_ruleset(rs);
4737 err_code |= ERR_ALERT | ERR_FATAL;
4738 goto out;
4739}
4740
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004741int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004742 const char *file, int line)
4743{
4744 static char *ldap_req = "300C020101600702010304008000";
4745
4746 struct tcpcheck_ruleset *rs = NULL;
4747 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4748 struct tcpcheck_rule *chk;
4749 char *errmsg = NULL;
4750 int err_code = 0;
4751
4752 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4753 err_code |= ERR_WARN;
4754
4755 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4756 goto out;
4757
4758 curpx->options2 &= ~PR_O2_CHK_ANY;
4759 curpx->options2 |= PR_O2_TCPCHK_CHK;
4760
4761 free_tcpcheck_vars(&rules->preset_vars);
4762 rules->list = NULL;
4763 rules->flags = 0;
4764
4765 rs = find_tcpcheck_ruleset("*ldap-check");
4766 if (rs)
4767 goto ruleset_found;
4768
4769 rs = create_tcpcheck_ruleset("*ldap-check");
4770 if (rs == NULL) {
4771 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4772 goto error;
4773 }
4774
4775 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4776 1, curpx, &rs->rules, file, line, &errmsg);
4777 if (!chk) {
4778 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4779 goto error;
4780 }
4781 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004782 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004783
4784 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4785 "min-recv", "14",
4786 "on-error", "Not LDAPv3 protocol",
4787 ""},
4788 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4789 if (!chk) {
4790 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4791 goto error;
4792 }
4793 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004794 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004795
4796 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4797 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4798 if (!chk) {
4799 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4800 goto error;
4801 }
4802 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4803 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004804 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004805
4806 ruleset_found:
4807 rules->list = &rs->rules;
4808 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4809 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4810
4811 out:
4812 free(errmsg);
4813 return err_code;
4814
4815 error:
4816 free_tcpcheck_ruleset(rs);
4817 err_code |= ERR_ALERT | ERR_FATAL;
4818 goto out;
4819}
4820
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004821int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004822 const char *file, int line)
4823{
4824 struct tcpcheck_ruleset *rs = NULL;
4825 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4826 struct tcpcheck_rule *chk;
4827 char *spop_req = NULL;
4828 char *errmsg = NULL;
4829 int spop_len = 0, err_code = 0;
4830
4831 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4832 err_code |= ERR_WARN;
4833
4834 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4835 goto out;
4836
4837 curpx->options2 &= ~PR_O2_CHK_ANY;
4838 curpx->options2 |= PR_O2_TCPCHK_CHK;
4839
4840 free_tcpcheck_vars(&rules->preset_vars);
4841 rules->list = NULL;
4842 rules->flags = 0;
4843
4844
4845 rs = find_tcpcheck_ruleset("*spop-check");
4846 if (rs)
4847 goto ruleset_found;
4848
4849 rs = create_tcpcheck_ruleset("*spop-check");
4850 if (rs == NULL) {
4851 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4852 goto error;
4853 }
4854
4855 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4856 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4857 goto error;
4858 }
4859 chunk_reset(&trash);
4860 dump_binary(&trash, spop_req, spop_len);
4861 trash.area[trash.data] = '\0';
4862
4863 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4864 1, curpx, &rs->rules, file, line, &errmsg);
4865 if (!chk) {
4866 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4867 goto error;
4868 }
4869 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004870 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004871
4872 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4873 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4874 if (!chk) {
4875 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4876 goto error;
4877 }
4878 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4879 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004880 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004881
4882 ruleset_found:
4883 rules->list = &rs->rules;
4884 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4885 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4886
4887 out:
4888 free(spop_req);
4889 free(errmsg);
4890 return err_code;
4891
4892 error:
4893 free_tcpcheck_ruleset(rs);
4894 err_code |= ERR_ALERT | ERR_FATAL;
4895 goto out;
4896}
4897
4898
4899static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4900{
4901 struct tcpcheck_rule *chk = NULL;
4902 struct tcpcheck_http_hdr *hdr = NULL;
4903 char *meth = NULL, *uri = NULL, *vsn = NULL;
4904 char *hdrs, *body;
4905
4906 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4907 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4908 if (hdrs == body)
4909 hdrs = NULL;
4910 if (hdrs) {
4911 *hdrs = '\0';
4912 hdrs +=2;
4913 }
4914 if (body) {
4915 *body = '\0';
4916 body += 4;
4917 }
4918 if (hdrs || body) {
4919 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4920 " Please, consider to use 'http-check send' directive instead.");
4921 }
4922
4923 chk = calloc(1, sizeof(*chk));
4924 if (!chk) {
4925 memprintf(errmsg, "out of memory");
4926 goto error;
4927 }
4928 chk->action = TCPCHK_ACT_SEND;
4929 chk->send.type = TCPCHK_SEND_HTTP;
4930 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4931 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4932 LIST_INIT(&chk->send.http.hdrs);
4933
4934 /* Copy the method, uri and version */
4935 if (*args[cur_arg]) {
4936 if (!*args[cur_arg+1])
4937 uri = args[cur_arg];
4938 else
4939 meth = args[cur_arg];
4940 }
4941 if (*args[cur_arg+1])
4942 uri = args[cur_arg+1];
4943 if (*args[cur_arg+2])
4944 vsn = args[cur_arg+2];
4945
4946 if (meth) {
4947 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4948 chk->send.http.meth.str.area = strdup(meth);
4949 chk->send.http.meth.str.data = strlen(meth);
4950 if (!chk->send.http.meth.str.area) {
4951 memprintf(errmsg, "out of memory");
4952 goto error;
4953 }
4954 }
4955 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004956 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004957 if (!isttest(chk->send.http.uri)) {
4958 memprintf(errmsg, "out of memory");
4959 goto error;
4960 }
4961 }
4962 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004963 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004964 if (!isttest(chk->send.http.vsn)) {
4965 memprintf(errmsg, "out of memory");
4966 goto error;
4967 }
4968 }
4969
4970 /* Copy the header */
4971 if (hdrs) {
4972 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4973 struct h1m h1m;
4974 int i, ret;
4975
4976 /* Build and parse the request */
4977 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4978
4979 h1m.flags = H1_MF_HDRS_ONLY;
4980 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4981 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4982 &h1m, NULL);
4983 if (ret <= 0) {
4984 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4985 goto error;
4986 }
4987
4988 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4989 hdr = calloc(1, sizeof(*hdr));
4990 if (!hdr) {
4991 memprintf(errmsg, "out of memory");
4992 goto error;
4993 }
4994 LIST_INIT(&hdr->value);
4995 hdr->name = istdup(tmp_hdrs[i].n);
Tim Duesterhus77508502022-03-15 13:11:06 +01004996 if (!isttest(hdr->name)) {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004997 memprintf(errmsg, "out of memory");
4998 goto error;
4999 }
5000
5001 ist0(tmp_hdrs[i].v);
5002 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5003 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005004 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005005 }
5006 }
5007
5008 /* Copy the body */
5009 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005010 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005011 if (!isttest(chk->send.http.body)) {
5012 memprintf(errmsg, "out of memory");
5013 goto error;
5014 }
5015 }
5016
5017 return chk;
5018
5019 error:
5020 free_tcpcheck_http_hdr(hdr);
5021 free_tcpcheck(chk, 0);
5022 return NULL;
5023}
5024
5025/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005026int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005027 const char *file, int line)
5028{
5029 struct tcpcheck_ruleset *rs = NULL;
5030 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5031 struct tcpcheck_rule *chk;
5032 char *errmsg = NULL;
5033 int err_code = 0;
5034
5035 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5036 err_code |= ERR_WARN;
5037
5038 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5039 goto out;
5040
5041 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5042 if (!chk) {
5043 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5044 goto error;
5045 }
5046 if (errmsg) {
5047 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5048 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005049 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005050 }
5051
5052 no_request:
5053 curpx->options2 &= ~PR_O2_CHK_ANY;
5054 curpx->options2 |= PR_O2_TCPCHK_CHK;
5055
5056 free_tcpcheck_vars(&rules->preset_vars);
5057 rules->list = NULL;
5058 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5059
5060 /* Deduce the ruleset name from the proxy info */
5061 chunk_printf(&trash, "*http-check-%s_%s-%d",
5062 ((curpx == defpx) ? "defaults" : curpx->id),
5063 curpx->conf.file, curpx->conf.line);
5064
5065 rs = find_tcpcheck_ruleset(b_orig(&trash));
5066 if (rs == NULL) {
5067 rs = create_tcpcheck_ruleset(b_orig(&trash));
5068 if (rs == NULL) {
5069 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5070 goto error;
5071 }
5072 }
5073
5074 rules->list = &rs->rules;
5075 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5076 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5077 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5078 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5079 rules->list = NULL;
5080 goto error;
5081 }
5082
5083 out:
5084 free(errmsg);
5085 return err_code;
5086
5087 error:
5088 free_tcpcheck_ruleset(rs);
5089 free_tcpcheck(chk, 0);
5090 err_code |= ERR_ALERT | ERR_FATAL;
5091 goto out;
5092}
5093
5094/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005095int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005096 const char *file, int line)
5097{
5098 struct tcpcheck_ruleset *rs = NULL;
5099 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5100 int err_code = 0;
5101
5102 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5103 err_code |= ERR_WARN;
5104
5105 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5106 goto out;
5107
5108 curpx->options2 &= ~PR_O2_CHK_ANY;
5109 curpx->options2 |= PR_O2_TCPCHK_CHK;
5110
5111 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5112 /* If a tcp-check rulesset is already set, do nothing */
5113 if (rules->list)
5114 goto out;
5115
5116 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5117 * get it.
5118 */
5119 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5120 goto curpx_ruleset;
5121
5122 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5123 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5124 rs = find_tcpcheck_ruleset(b_orig(&trash));
5125 if (rs)
5126 goto ruleset_found;
5127 }
5128
5129 curpx_ruleset:
5130 /* Deduce the ruleset name from the proxy info */
5131 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5132 ((curpx == defpx) ? "defaults" : curpx->id),
5133 curpx->conf.file, curpx->conf.line);
5134
5135 rs = find_tcpcheck_ruleset(b_orig(&trash));
5136 if (rs == NULL) {
5137 rs = create_tcpcheck_ruleset(b_orig(&trash));
5138 if (rs == NULL) {
5139 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5140 goto error;
5141 }
5142 }
5143
5144 ruleset_found:
5145 free_tcpcheck_vars(&rules->preset_vars);
5146 rules->list = &rs->rules;
5147 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5148 rules->flags |= TCPCHK_RULES_TCP_CHK;
5149
5150 out:
5151 return err_code;
5152
5153 error:
5154 err_code |= ERR_ALERT | ERR_FATAL;
5155 goto out;
5156}
5157
Willy Tarreau51cd5952020-06-05 12:25:38 +02005158static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005159 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005160 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5161 { 0, NULL, NULL },
5162}};
5163
5164REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5165REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5166REGISTER_POST_DEINIT(deinit_tcpchecks);
5167INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);