blob: 1b8199b1ce7ce25cd00a8a1f08133742137a0c25 [file] [log] [blame]
Willy Tarreau51cd5952020-06-05 12:25:38 +02001/*
2 * Health-checks functions.
3 *
4 * Copyright 2000-2009,2020 Willy Tarreau <w@1wt.eu>
5 * Copyright 2007-2010 Krzysztof Piotr Oledzki <ole@ans.pl>
6 * Copyright 2013 Baptiste Assmann <bedis9@gmail.com>
7 * Copyright 2020 Gaetan Rivet <grive@u256.net>
8 * Copyright 2020 Christopher Faulet <cfaulet@haproxy.com>
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version
13 * 2 of the License, or (at your option) any later version.
14 *
15 */
16
17#include <sys/resource.h>
18#include <sys/socket.h>
19#include <sys/types.h>
20#include <sys/wait.h>
21#include <netinet/in.h>
22#include <netinet/tcp.h>
23#include <arpa/inet.h>
24
25#include <ctype.h>
26#include <errno.h>
27#include <fcntl.h>
28#include <signal.h>
29#include <stdarg.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <time.h>
34#include <unistd.h>
35
36#include <haproxy/action.h>
37#include <haproxy/api.h>
38#include <haproxy/cfgparse.h>
39#include <haproxy/check.h>
40#include <haproxy/chunk.h>
41#include <haproxy/connection.h>
Christopher Faulet1329f2a2021-12-16 17:32:56 +010042#include <haproxy/conn_stream.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020043#include <haproxy/errors.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020044#include <haproxy/global.h>
45#include <haproxy/h1.h>
46#include <haproxy/http.h>
47#include <haproxy/http_htx.h>
48#include <haproxy/htx.h>
49#include <haproxy/istbuf.h>
50#include <haproxy/list.h>
51#include <haproxy/log.h>
Christopher Faulet8a0e5f82021-09-16 16:01:09 +020052#include <haproxy/net_helper.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020053#include <haproxy/protocol.h>
54#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020055#include <haproxy/regex.h>
56#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020057#include <haproxy/server.h>
58#include <haproxy/ssl_sock.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020059#include <haproxy/task.h>
60#include <haproxy/tcpcheck.h>
Willy Tarreau9310f482021-10-06 16:18:40 +020061#include <haproxy/ticks.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020062#include <haproxy/tools.h>
Christopher Faulet147b8c92021-04-10 09:00:38 +020063#include <haproxy/trace.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020064#include <haproxy/vars.h>
65
66
Christopher Faulet147b8c92021-04-10 09:00:38 +020067#define TRACE_SOURCE &trace_check
68
Willy Tarreau51cd5952020-06-05 12:25:38 +020069/* Global tree to share all tcp-checks */
70struct eb_root shared_tcpchecks = EB_ROOT;
71
72
73DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
74
75/**************************************************************************/
76/*************** Init/deinit tcp-check rules and ruleset ******************/
77/**************************************************************************/
78/* Releases memory allocated for a log-format string */
79static void free_tcpcheck_fmt(struct list *fmt)
80{
81 struct logformat_node *lf, *lfb;
82
83 list_for_each_entry_safe(lf, lfb, fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +020084 LIST_DELETE(&lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +020085 release_sample_expr(lf->expr);
86 free(lf->arg);
87 free(lf);
88 }
89}
90
91/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
92void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
93{
94 if (!hdr)
95 return;
96
97 free_tcpcheck_fmt(&hdr->value);
98 istfree(&hdr->name);
99 free(hdr);
100}
101
102/* Releases memory allocated for an HTTP header list used in a tcp-check send
103 * rule
104 */
105static void free_tcpcheck_http_hdrs(struct list *hdrs)
106{
107 struct tcpcheck_http_hdr *hdr, *bhdr;
108
109 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200110 LIST_DELETE(&hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200111 free_tcpcheck_http_hdr(hdr);
112 }
113}
114
115/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
116 * tcp-check was allocated using a memory pool (it is used to instantiate email
117 * alerts).
118 */
119void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
120{
121 if (!rule)
122 return;
123
124 free(rule->comment);
125 switch (rule->action) {
126 case TCPCHK_ACT_SEND:
127 switch (rule->send.type) {
128 case TCPCHK_SEND_STRING:
129 case TCPCHK_SEND_BINARY:
130 istfree(&rule->send.data);
131 break;
132 case TCPCHK_SEND_STRING_LF:
133 case TCPCHK_SEND_BINARY_LF:
134 free_tcpcheck_fmt(&rule->send.fmt);
135 break;
136 case TCPCHK_SEND_HTTP:
137 free(rule->send.http.meth.str.area);
138 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
139 istfree(&rule->send.http.uri);
140 else
141 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
142 istfree(&rule->send.http.vsn);
143 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
144 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
145 istfree(&rule->send.http.body);
146 else
147 free_tcpcheck_fmt(&rule->send.http.body_fmt);
148 break;
149 case TCPCHK_SEND_UNDEF:
150 break;
151 }
152 break;
153 case TCPCHK_ACT_EXPECT:
154 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
155 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
156 release_sample_expr(rule->expect.status_expr);
157 switch (rule->expect.type) {
158 case TCPCHK_EXPECT_HTTP_STATUS:
159 free(rule->expect.codes.codes);
160 break;
161 case TCPCHK_EXPECT_STRING:
162 case TCPCHK_EXPECT_BINARY:
163 case TCPCHK_EXPECT_HTTP_BODY:
164 istfree(&rule->expect.data);
165 break;
166 case TCPCHK_EXPECT_STRING_REGEX:
167 case TCPCHK_EXPECT_BINARY_REGEX:
168 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
169 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
170 regex_free(rule->expect.regex);
171 break;
172 case TCPCHK_EXPECT_STRING_LF:
173 case TCPCHK_EXPECT_BINARY_LF:
174 case TCPCHK_EXPECT_HTTP_BODY_LF:
175 free_tcpcheck_fmt(&rule->expect.fmt);
176 break;
177 case TCPCHK_EXPECT_HTTP_HEADER:
178 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
179 regex_free(rule->expect.hdr.name_re);
180 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
181 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
182 else
183 istfree(&rule->expect.hdr.name);
184
185 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
186 regex_free(rule->expect.hdr.value_re);
187 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
188 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
189 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
190 istfree(&rule->expect.hdr.value);
191 break;
192 case TCPCHK_EXPECT_CUSTOM:
193 case TCPCHK_EXPECT_UNDEF:
194 break;
195 }
196 break;
197 case TCPCHK_ACT_CONNECT:
198 free(rule->connect.sni);
199 free(rule->connect.alpn);
200 release_sample_expr(rule->connect.port_expr);
201 break;
202 case TCPCHK_ACT_COMMENT:
203 break;
204 case TCPCHK_ACT_ACTION_KW:
205 free(rule->action_kw.rule);
206 break;
207 }
208
209 if (in_pool)
210 pool_free(pool_head_tcpcheck_rule, rule);
211 else
212 free(rule);
213}
214
215/* Creates a tcp-check variable used in preset variables before executing a
216 * tcp-check ruleset.
217 */
218struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
219{
220 struct tcpcheck_var *var = NULL;
221
222 var = calloc(1, sizeof(*var));
223 if (var == NULL)
224 return NULL;
225
226 var->name = istdup(name);
227 if (!isttest(var->name)) {
228 free(var);
229 return NULL;
230 }
231
232 LIST_INIT(&var->list);
233 return var;
234}
235
236/* Releases memory allocated for a preset tcp-check variable */
237void free_tcpcheck_var(struct tcpcheck_var *var)
238{
239 if (!var)
240 return;
241
242 istfree(&var->name);
243 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
244 free(var->data.u.str.area);
245 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
246 free(var->data.u.meth.str.area);
247 free(var);
248}
249
250/* Releases a list of preset tcp-check variables */
251void free_tcpcheck_vars(struct list *vars)
252{
253 struct tcpcheck_var *var, *back;
254
255 list_for_each_entry_safe(var, back, vars, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200256 LIST_DELETE(&var->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200257 free_tcpcheck_var(var);
258 }
259}
260
261/* Duplicate a list of preset tcp-check variables */
Willy Tarreau09f2e772021-02-12 08:42:30 +0100262int dup_tcpcheck_vars(struct list *dst, const struct list *src)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200263{
Willy Tarreau09f2e772021-02-12 08:42:30 +0100264 const struct tcpcheck_var *var;
265 struct tcpcheck_var *new = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200266
267 list_for_each_entry(var, src, list) {
268 new = create_tcpcheck_var(var->name);
269 if (!new)
270 goto error;
271 new->data.type = var->data.type;
272 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
273 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
274 goto error;
275 if (var->data.type == SMP_T_STR)
276 new->data.u.str.area[new->data.u.str.data] = 0;
277 }
278 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
279 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
280 goto error;
281 new->data.u.str.area[new->data.u.str.data] = 0;
282 new->data.u.meth.meth = var->data.u.meth.meth;
283 }
284 else
285 new->data.u = var->data.u;
Willy Tarreau2b718102021-04-21 07:32:39 +0200286 LIST_APPEND(dst, &new->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200287 }
288 return 1;
289
290 error:
291 free(new);
292 return 0;
293}
294
295/* Looks for a shared tcp-check ruleset given its name. */
296struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
297{
298 struct tcpcheck_ruleset *rs;
299 struct ebpt_node *node;
300
301 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
302 if (node) {
303 rs = container_of(node, typeof(*rs), node);
304 return rs;
305 }
306 return NULL;
307}
308
309/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks
310 * tree.
311 */
312struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
313{
314 struct tcpcheck_ruleset *rs;
315
316 rs = calloc(1, sizeof(*rs));
317 if (rs == NULL)
318 return NULL;
319
320 rs->node.key = strdup(name);
321 if (rs->node.key == NULL) {
322 free(rs);
323 return NULL;
324 }
325
326 LIST_INIT(&rs->rules);
327 ebis_insert(&shared_tcpchecks, &rs->node);
328 return rs;
329}
330
331/* Releases memory allocated by a tcp-check ruleset. */
332void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
333{
334 struct tcpcheck_rule *r, *rb;
335
336 if (!rs)
337 return;
338
339 ebpt_delete(&rs->node);
340 free(rs->node.key);
341 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200342 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200343 free_tcpcheck(r, 0);
344 }
345 free(rs);
346}
347
348
349/**************************************************************************/
350/**************** Everything about tcp-checks execution *******************/
351/**************************************************************************/
352/* Returns the id of a step in a tcp-check ruleset */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200353int tcpcheck_get_step_id(const struct check *check, const struct tcpcheck_rule *rule)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200354{
355 if (!rule)
356 rule = check->current_step;
357
358 /* no last started step => first step */
359 if (!rule)
360 return 1;
361
362 /* last step is the first implicit connect */
363 if (rule->index == 0 &&
364 rule->action == TCPCHK_ACT_CONNECT &&
365 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
366 return 0;
367
368 return rule->index + 1;
369}
370
371/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
372 * NULL if none was found.
373 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200374struct tcpcheck_rule *get_first_tcpcheck_rule(const struct tcpcheck_rules *rules)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200375{
376 struct tcpcheck_rule *r;
377
378 list_for_each_entry(r, rules->list, list) {
379 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
380 return r;
381 }
382 return NULL;
383}
384
385/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
386 * NULL if none was found.
387 */
388static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
389{
390 struct tcpcheck_rule *r;
391
392 list_for_each_entry_rev(r, rules->list, list) {
393 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
394 return r;
395 }
396 return NULL;
397}
398
399/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
400 * <start> or NULL if non was found. If <start> is NULL, it relies on
401 * get_first_tcpcheck_rule().
402 */
403static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
404{
405 struct tcpcheck_rule *r;
406
407 if (!start)
408 return get_first_tcpcheck_rule(rules);
409
410 r = LIST_NEXT(&start->list, typeof(r), list);
411 list_for_each_entry_from(r, rules->list, list) {
412 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
413 return r;
414 }
415 return NULL;
416}
417
418
419/* Creates info message when a tcp-check healthcheck fails on an expect rule */
420static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
421 int match, struct ist info)
422{
423 struct sample *smp;
424
425 /* Follows these step to produce the info message:
426 * 1. if info field is already provided, copy it
427 * 2. if the expect rule provides an onerror log-format string,
428 * use it to produce the message
429 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
430 * 4. Otherwise produce the generic tcp-check info message
431 */
432 if (istlen(info)) {
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100433 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200434 goto comment;
435 }
436 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
437 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
438 goto comment;
439 }
440
441 if (check->type == PR_O2_TCPCHK_CHK &&
442 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
443 goto comment;
444
445 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
446 switch (rule->expect.type) {
447 case TCPCHK_EXPECT_HTTP_STATUS:
448 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
449 break;
450 case TCPCHK_EXPECT_STRING:
451 case TCPCHK_EXPECT_HTTP_BODY:
452 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
453 tcpcheck_get_step_id(check, rule));
454 break;
455 case TCPCHK_EXPECT_BINARY:
456 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
457 break;
458 case TCPCHK_EXPECT_STRING_REGEX:
459 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
460 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
461 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
462 break;
463 case TCPCHK_EXPECT_BINARY_REGEX:
464 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
465 break;
466 case TCPCHK_EXPECT_STRING_LF:
467 case TCPCHK_EXPECT_HTTP_BODY_LF:
468 chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule));
469 break;
470 case TCPCHK_EXPECT_BINARY_LF:
471 chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule));
472 break;
473 case TCPCHK_EXPECT_CUSTOM:
474 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
475 break;
476 case TCPCHK_EXPECT_HTTP_HEADER:
477 chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
478 case TCPCHK_EXPECT_UNDEF:
479 /* Should never happen. */
480 return;
481 }
482
483 comment:
484 /* If the failing expect rule provides a comment, it is concatenated to
485 * the info message.
486 */
487 if (rule->comment) {
488 chunk_strcat(msg, " comment: ");
489 chunk_strcat(msg, rule->comment);
490 }
491
492 /* Finally, the check status code is set if the failing expect rule
493 * defines a status expression.
494 */
495 if (rule->expect.status_expr) {
496 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
497 rule->expect.status_expr, SMP_T_STR);
498
499 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
500 sample_casts[smp->data.type][SMP_T_SINT](smp))
501 check->code = smp->data.u.sint;
502 }
503
504 *(b_tail(msg)) = '\0';
505}
506
507/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
508static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
509 struct ist info)
510{
511 struct sample *smp;
512
513 /* Follows these step to produce the info message:
514 * 1. if info field is already provided, copy it
515 * 2. if the expect rule provides an onsucces log-format string,
516 * use it to produce the message
517 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
518 * 4. Otherwise produce the generic tcp-check info message
519 */
520 if (istlen(info))
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100521 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200522 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
523 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
524 &rule->expect.onsuccess_fmt);
525 else if (check->type == PR_O2_TCPCHK_CHK &&
526 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
527 chunk_strcat(msg, "(tcp-check)");
528
529 /* Finally, the check status code is set if the expect rule defines a
530 * status expression.
531 */
532 if (rule->expect.status_expr) {
533 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
534 rule->expect.status_expr, SMP_T_STR);
535
536 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
537 sample_casts[smp->data.type][SMP_T_SINT](smp))
538 check->code = smp->data.u.sint;
539 }
540
541 *(b_tail(msg)) = '\0';
542}
543
544/* Internal functions to parse and validate a MySQL packet in the context of an
545 * expect rule. It start to parse the input buffer at the offset <offset>. If
546 * <last_read> is set, no more data are expected.
547 */
548static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
549 unsigned int offset, int last_read)
550{
551 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
552 enum healthcheck_status status;
553 struct buffer *msg = NULL;
554 struct ist desc = IST_NULL;
555 unsigned int err = 0, plen = 0;
556
557
Christopher Faulet147b8c92021-04-10 09:00:38 +0200558 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
559
Willy Tarreau51cd5952020-06-05 12:25:38 +0200560 /* 3 Bytes for the packet length and 1 byte for the sequence id */
561 if (b_data(&check->bi) < offset+4) {
562 if (!last_read)
563 goto wait_more_data;
564
565 /* invalid length or truncated response */
566 status = HCHK_STATUS_L7RSP;
567 goto error;
568 }
569
570 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
571 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
572 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
573
574 if (b_data(&check->bi) < offset+plen+4) {
575 if (!last_read)
576 goto wait_more_data;
577
578 /* invalid length or truncated response */
579 status = HCHK_STATUS_L7RSP;
580 goto error;
581 }
582
583 if (*b_peek(&check->bi, offset+4) == '\xff') {
584 /* MySQL Error packet always begin with field_count = 0xff */
585 status = HCHK_STATUS_L7STS;
586 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
587 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
588 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
589 goto error;
590 }
591
592 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
593 /* Not the last rule, continue */
594 goto out;
595 }
596
597 /* We set the MySQL Version in description for information purpose
598 * FIXME : it can be cool to use MySQL Version for other purpose,
599 * like mark as down old MySQL server.
600 */
601 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
602 set_server_check_status(check, status, b_peek(&check->bi, 5));
603
604 out:
605 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200606 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200607 return ret;
608
609 error:
610 ret = TCPCHK_EVAL_STOP;
611 check->code = err;
612 msg = alloc_trash_chunk();
613 if (msg)
614 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
615 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
616 goto out;
617
618 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200619 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200620 ret = TCPCHK_EVAL_WAIT;
621 goto out;
622}
623
624/* Custom tcp-check expect function to parse and validate the MySQL initial
625 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
626 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
627 * error occurred.
628 */
629enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
630{
631 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
632}
633
634/* Custom tcp-check expect function to parse and validate the MySQL OK packet
635 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
636 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
637 * an error occurred.
638 */
639enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
640{
641 unsigned int hslen = 0;
642
643 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
644 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
645 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
646
647 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
648}
649
650/* Custom tcp-check expect function to parse and validate the LDAP bind response
651 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
652 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
653 * error occurred.
654 */
655enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
656{
657 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
658 enum healthcheck_status status;
659 struct buffer *msg = NULL;
660 struct ist desc = IST_NULL;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200661 char *ptr;
662 unsigned short nbytes = 0;
663 size_t msglen = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200664
Christopher Faulet147b8c92021-04-10 09:00:38 +0200665 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
666
Willy Tarreau51cd5952020-06-05 12:25:38 +0200667 /* Check if the server speaks LDAP (ASN.1/BER)
668 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
669 * http://tools.ietf.org/html/rfc4511
670 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200671 ptr = b_head(&check->bi) + 1;
672
Willy Tarreau51cd5952020-06-05 12:25:38 +0200673 /* size of LDAPMessage */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200674 if (*ptr & 0x80) {
675 /* For message size encoded on several bytes, we only handle
676 * size encoded on 2 or 4 bytes. There is no reason to make this
677 * part to complex because only Active Directory is known to
678 * encode BindReponse length on 4 bytes.
679 */
680 nbytes = (*ptr & 0x7f);
681 if (b_data(&check->bi) < 1 + nbytes)
682 goto too_short;
683 switch (nbytes) {
684 case 4: msglen = read_n32(ptr+1); break;
685 case 2: msglen = read_n16(ptr+1); break;
686 default:
687 status = HCHK_STATUS_L7RSP;
688 desc = ist("Not LDAPv3 protocol");
689 goto error;
690 }
691 }
692 else
693 msglen = *ptr;
694 ptr += 1 + nbytes;
695
696 if (b_data(&check->bi) < 2 + nbytes + msglen)
697 goto too_short;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200698
699 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
700 * messageID: 0x02 0x01 0x01: INTEGER 1
701 * protocolOp: 0x61: bindResponse
702 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200703 if (memcmp(ptr, "\x02\x01\x01\x61", 4) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200704 status = HCHK_STATUS_L7RSP;
705 desc = ist("Not LDAPv3 protocol");
706 goto error;
707 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200708 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200709
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200710 /* skip size of bindResponse */
711 nbytes = 0;
712 if (*ptr & 0x80)
713 nbytes = (*ptr & 0x7f);
714 ptr += 1 + nbytes;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200715
716 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
717 * ldapResult: 0x0a 0x01: ENUMERATION
718 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200719 if (memcmp(ptr, "\x0a\x01", 2) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200720 status = HCHK_STATUS_L7RSP;
721 desc = ist("Not LDAPv3 protocol");
722 goto error;
723 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200724 ptr += 2;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200725
726 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
727 * resultCode
728 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200729 check->code = *ptr;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200730 if (check->code) {
731 status = HCHK_STATUS_L7STS;
732 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
733 goto error;
734 }
735
736 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
737 set_server_check_status(check, status, "Success");
738
739 out:
740 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200741 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200742 return ret;
743
744 error:
745 ret = TCPCHK_EVAL_STOP;
746 msg = alloc_trash_chunk();
747 if (msg)
748 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
749 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
750 goto out;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200751
752 too_short:
753 if (!last_read)
754 goto wait_more_data;
755 /* invalid length or truncated response */
756 status = HCHK_STATUS_L7RSP;
757 goto error;
758
759 wait_more_data:
760 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
761 ret = TCPCHK_EVAL_WAIT;
762 goto out;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200763}
764
765/* Custom tcp-check expect function to parse and validate the SPOP hello agent
766 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
767 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
768 */
769enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
770{
771 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
772 enum healthcheck_status status;
773 struct buffer *msg = NULL;
774 struct ist desc = IST_NULL;
775 unsigned int framesz;
776
Christopher Faulet147b8c92021-04-10 09:00:38 +0200777 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200778
779 memcpy(&framesz, b_head(&check->bi), 4);
780 framesz = ntohl(framesz);
781
782 if (!last_read && b_data(&check->bi) < (4+framesz))
783 goto wait_more_data;
784
785 memset(b_orig(&trash), 0, b_size(&trash));
786 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
787 status = HCHK_STATUS_L7RSP;
788 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
789 goto error;
790 }
791
792 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
793 set_server_check_status(check, status, "SPOA server is ok");
794
795 out:
796 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200797 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200798 return ret;
799
800 error:
801 ret = TCPCHK_EVAL_STOP;
802 msg = alloc_trash_chunk();
803 if (msg)
804 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
805 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
806 goto out;
807
808 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200809 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200810 ret = TCPCHK_EVAL_WAIT;
811 goto out;
812}
813
814/* Custom tcp-check expect function to parse and validate the agent-check
815 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
816 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
817 */
818enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
819{
820 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
821 enum healthcheck_status status = HCHK_STATUS_CHECKED;
822 const char *hs = NULL; /* health status */
823 const char *as = NULL; /* admin status */
824 const char *ps = NULL; /* performance status */
825 const char *cs = NULL; /* maxconn */
826 const char *err = NULL; /* first error to report */
827 const char *wrn = NULL; /* first warning to report */
828 char *cmd, *p;
829
Christopher Faulet147b8c92021-04-10 09:00:38 +0200830 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
831
Willy Tarreau51cd5952020-06-05 12:25:38 +0200832 /* We're getting an agent check response. The agent could
833 * have been disabled in the mean time with a long check
834 * still pending. It is important that we ignore the whole
835 * response.
836 */
837 if (!(check->state & CHK_ST_ENABLED))
838 goto out;
839
840 /* The agent supports strings made of a single line ended by the
841 * first CR ('\r') or LF ('\n'). This line is composed of words
842 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
843 * line may optionally contained a description of a state change
844 * after a sharp ('#'), which is only considered if a health state
845 * is announced.
846 *
847 * Words may be composed of :
848 * - a numeric weight suffixed by the percent character ('%').
849 * - a health status among "up", "down", "stopped", and "fail".
850 * - an admin status among "ready", "drain", "maint".
851 *
852 * These words may appear in any order. If multiple words of the
853 * same category appear, the last one wins.
854 */
855
856 p = b_head(&check->bi);
857 while (*p && *p != '\n' && *p != '\r')
858 p++;
859
860 if (!*p) {
861 if (!last_read)
862 goto wait_more_data;
863
864 /* at least inform the admin that the agent is mis-behaving */
865 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
866 goto out;
867 }
868
869 *p = 0;
870 cmd = b_head(&check->bi);
871
872 while (*cmd) {
873 /* look for next word */
874 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
875 cmd++;
876 continue;
877 }
878
879 if (*cmd == '#') {
880 /* this is the beginning of a health status description,
881 * skip the sharp and blanks.
882 */
883 cmd++;
884 while (*cmd == '\t' || *cmd == ' ')
885 cmd++;
886 break;
887 }
888
889 /* find the end of the word so that we have a null-terminated
890 * word between <cmd> and <p>.
891 */
892 p = cmd + 1;
893 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
894 p++;
895 if (*p)
896 *p++ = 0;
897
898 /* first, health statuses */
899 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100900 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200901 status = HCHK_STATUS_L7OKD;
902 hs = cmd;
903 }
904 else if (strcasecmp(cmd, "down") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100905 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200906 status = HCHK_STATUS_L7STS;
907 hs = cmd;
908 }
909 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100910 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200911 status = HCHK_STATUS_L7STS;
912 hs = cmd;
913 }
914 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100915 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200916 status = HCHK_STATUS_L7STS;
917 hs = cmd;
918 }
919 /* admin statuses */
920 else if (strcasecmp(cmd, "ready") == 0) {
921 as = cmd;
922 }
923 else if (strcasecmp(cmd, "drain") == 0) {
924 as = cmd;
925 }
926 else if (strcasecmp(cmd, "maint") == 0) {
927 as = cmd;
928 }
929 /* try to parse a weight here and keep the last one */
930 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
931 ps = cmd;
932 }
933 /* try to parse a maxconn here */
934 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
935 cs = cmd;
936 }
937 else {
938 /* keep a copy of the first error */
939 if (!err)
940 err = cmd;
941 }
942 /* skip to next word */
943 cmd = p;
944 }
945 /* here, cmd points either to \0 or to the beginning of a
946 * description. Skip possible leading spaces.
947 */
948 while (*cmd == ' ' || *cmd == '\n')
949 cmd++;
950
951 /* First, update the admin status so that we avoid sending other
952 * possibly useless warnings and can also update the health if
953 * present after going back up.
954 */
955 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200956 if (strcasecmp(as, "drain") == 0) {
957 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200958 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200959 }
960 else if (strcasecmp(as, "maint") == 0) {
961 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200962 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200963 }
964 else {
965 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200966 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200967 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200968 }
969
970 /* now change weights */
971 if (ps) {
972 const char *msg;
973
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500974 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200975 msg = server_parse_weight_change_request(check->server, ps);
976 if (!wrn || !*wrn)
977 wrn = msg;
978 }
979
980 if (cs) {
981 const char *msg;
982
983 cs += strlen("maxconn:");
984
Christopher Faulet147b8c92021-04-10 09:00:38 +0200985 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury Denoyelle02742862021-06-18 11:11:36 +0200986 /* This is safe to call server_parse_maxconn_change_request
987 * because the server lock is held during the check.
988 */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200989 msg = server_parse_maxconn_change_request(check->server, cs);
990 if (!wrn || !*wrn)
991 wrn = msg;
992 }
993
994 /* and finally health status */
995 if (hs) {
996 /* We'll report some of the warnings and errors we have
997 * here. Down reports are critical, we leave them untouched.
998 * Lack of report, or report of 'UP' leaves the room for
999 * ERR first, then WARN.
1000 */
1001 const char *msg = cmd;
1002 struct buffer *t;
1003
1004 if (!*msg || status == HCHK_STATUS_L7OKD) {
1005 if (err && *err)
1006 msg = err;
1007 else if (wrn && *wrn)
1008 msg = wrn;
1009 }
1010
1011 t = get_trash_chunk();
1012 chunk_printf(t, "via agent : %s%s%s%s",
1013 hs, *msg ? " (" : "",
1014 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001015 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001016 set_server_check_status(check, status, t->area);
1017 }
1018 else if (err && *err) {
1019 /* No status change but we'd like to report something odd.
1020 * Just report the current state and copy the message.
1021 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001022 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001023 chunk_printf(&trash, "agent reports an error : %s", err);
1024 set_server_check_status(check, status/*check->status*/, trash.area);
1025 }
1026 else if (wrn && *wrn) {
1027 /* No status change but we'd like to report something odd.
1028 * Just report the current state and copy the message.
1029 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001030 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001031 chunk_printf(&trash, "agent warns : %s", wrn);
1032 set_server_check_status(check, status/*check->status*/, trash.area);
1033 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001034 else {
1035 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001036 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001037 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001038
1039 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001040 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001041 return ret;
1042
1043 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001044 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001045 ret = TCPCHK_EVAL_WAIT;
1046 goto out;
1047}
1048
1049/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1050 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1051 * TCPCHK_EVAL_STOP if an error occurred.
1052 */
1053enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1054{
1055 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1056 struct tcpcheck_connect *connect = &rule->connect;
1057 struct proxy *proxy = check->proxy;
1058 struct server *s = check->server;
1059 struct task *t = check->task;
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001060 struct connection *conn = cs_conn(check->cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001061 struct protocol *proto;
1062 struct xprt_ops *xprt;
1063 struct tcpcheck_rule *next;
1064 int status, port;
1065
Christopher Faulet147b8c92021-04-10 09:00:38 +02001066 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1067
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001068 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1069
1070 /* current connection already created, check if it is established or not */
1071 if (conn) {
1072 if (conn->flags & CO_FL_WAIT_XPRT) {
1073 /* We are still waiting for the connection establishment */
1074 if (next && next->action == TCPCHK_ACT_SEND) {
1075 if (!(check->wait_list.events & SUB_RETRY_SEND))
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001076 conn->mux->subscribe(check->cs, SUB_RETRY_SEND, &check->wait_list);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001077 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001078 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001079 }
1080 else
1081 ret = tcpcheck_eval_recv(check, rule);
1082 }
1083 goto out;
1084 }
1085
1086 /* Note: here check->cs = cs = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001087
Christopher Fauletb381a502020-11-25 13:47:00 +01001088 /* Always release input and output buffer when a new connect is evaluated */
1089 check_release_buf(check, &check->bi);
1090 check_release_buf(check, &check->bo);
1091
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001092 /* No connection, prepare a new one */
Christopher Fauletdd2d0d82021-12-20 09:34:32 +01001093 conn = conn_new((s ? &s->obj_type : &proxy->obj_type));
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001094 if (!conn) {
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001095 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1096 tcpcheck_get_step_id(check, rule));
1097 if (rule->comment)
1098 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1099 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1100 ret = TCPCHK_EVAL_STOP;
1101 TRACE_ERROR("conn-stream allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001102 goto out;
1103 }
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001104 cs_attach_endp(check->cs, &conn->obj_type, conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001105 tasklet_set_tid(check->wait_list.tasklet, tid);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001106 conn_set_owner(conn, check->sess, NULL);
1107
1108 /* Maybe there were an older connection we were waiting on */
1109 check->wait_list.events = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001110
1111 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001112 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001113 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001114 status = SF_ERR_RESOURCE;
1115 goto fail_check;
1116 }
1117
1118 /* connect to the connect rule addr if specified, otherwise the check
1119 * addr if specified on the server. otherwise, use the server addr (it
1120 * MUST exist at this step).
1121 */
1122 *conn->dst = (is_addr(&connect->addr)
1123 ? connect->addr
1124 : (is_addr(&check->addr) ? check->addr : s->addr));
Willy Tarreau14e7f292021-10-27 17:41:07 +02001125 proto = protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001126
1127 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001128 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001129 port = connect->port;
1130 if (!port && connect->port_expr) {
1131 struct sample *smp;
1132
1133 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1134 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1135 connect->port_expr, SMP_T_SINT);
1136 if (smp)
1137 port = smp->data.u.sint;
1138 }
1139 if (!port && is_inet_addr(&connect->addr))
1140 port = get_host_port(&connect->addr);
1141 if (!port && check->port)
1142 port = check->port;
1143 if (!port && is_inet_addr(&check->addr))
1144 port = get_host_port(&check->addr);
1145 if (!port) {
1146 /* The server MUST exist here */
1147 port = s->svc_port;
1148 }
1149 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001150 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001151
1152 xprt = ((connect->options & TCPCHK_OPT_SSL)
1153 ? xprt_get(XPRT_SSL)
1154 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1155
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001156 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001157 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001158 status = SF_ERR_RESOURCE;
1159 goto fail_check;
1160 }
1161
Christopher Fauletf7177272020-10-02 13:41:55 +02001162 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1163 conn->send_proxy_ofs = 1;
1164 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001165 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001166 }
1167 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1168 conn->send_proxy_ofs = 1;
1169 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001170 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001171 }
1172
1173 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1174 conn->send_proxy_ofs = 1;
1175 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001176 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001177 }
1178 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1179 conn->send_proxy_ofs = 1;
1180 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001181 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001182 }
1183
Willy Tarreau51cd5952020-06-05 12:25:38 +02001184 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001185 if (proto && proto->connect) {
1186 int flags = 0;
1187
1188 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1189 flags |= CONNECT_HAS_DATA;
1190 if (!next || next->action != TCPCHK_ACT_EXPECT)
1191 flags |= CONNECT_DELACK_ALWAYS;
1192 status = proto->connect(conn, flags);
1193 }
1194
1195 if (status != SF_ERR_NONE)
1196 goto fail_check;
1197
Christopher Faulet21ddc742020-07-01 15:26:14 +02001198 conn_set_private(conn);
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001199 conn->ctx = check->cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001200
Willy Tarreau51cd5952020-06-05 12:25:38 +02001201#ifdef USE_OPENSSL
1202 if (connect->sni)
1203 ssl_sock_set_servername(conn, connect->sni);
1204 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1205 ssl_sock_set_servername(conn, s->check.sni);
1206
1207 if (connect->alpn)
1208 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1209 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1210 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1211#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001212
1213 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1214 /* Some servers don't like reset on close */
Christopher Faulet897d6122021-12-17 17:28:35 +01001215 HA_ATOMIC_AND(&fdtab[conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001216 }
1217
1218 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1219 if (xprt_add_hs(conn) < 0)
1220 status = SF_ERR_RESOURCE;
1221 }
1222
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001223 if (conn_xprt_start(conn) < 0) {
1224 status = SF_ERR_RESOURCE;
1225 goto fail_check;
1226 }
1227
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001228 /* The mux may be initialized now if there isn't server attached to the
1229 * check (email alerts) or if there is a mux proto specified or if there
1230 * is no alpn.
1231 */
1232 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1233 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1234 const struct mux_ops *mux_ops;
1235
Christopher Faulet147b8c92021-04-10 09:00:38 +02001236 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001237 if (connect->mux_proto)
1238 mux_ops = connect->mux_proto->mux;
1239 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1240 mux_ops = check->mux_proto->mux;
1241 else {
1242 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1243 ? PROTO_MODE_HTTP
1244 : PROTO_MODE_TCP);
1245
1246 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1247 }
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001248 if (mux_ops && conn_install_mux(conn, mux_ops, check->cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001249 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001250 status = SF_ERR_INTERNAL;
1251 goto fail_check;
1252 }
1253 }
1254
Willy Tarreau51cd5952020-06-05 12:25:38 +02001255 fail_check:
1256 /* It can return one of :
1257 * - SF_ERR_NONE if everything's OK
1258 * - SF_ERR_SRVTO if there are no more servers
1259 * - SF_ERR_SRVCL if the connection was refused by the server
1260 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1261 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1262 * - SF_ERR_INTERNAL for any other purely internal errors
1263 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1264 * Note that we try to prevent the network stack from sending the ACK during the
1265 * connect() when a pure TCP check is used (without PROXY protocol).
1266 */
1267 switch (status) {
1268 case SF_ERR_NONE:
1269 /* we allow up to min(inter, timeout.connect) for a connection
1270 * to establish but only when timeout.check is set as it may be
1271 * to short for a full check otherwise
1272 */
1273 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1274
1275 if (proxy->timeout.check && proxy->timeout.connect) {
1276 int t_con = tick_add(now_ms, proxy->timeout.connect);
1277 t->expire = tick_first(t->expire, t_con);
1278 }
1279 break;
1280 case SF_ERR_SRVTO: /* ETIMEDOUT */
1281 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1282 case SF_ERR_PRXCOND:
1283 case SF_ERR_RESOURCE:
1284 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001285 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check, 0, 0, (size_t[]){status});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001286 chk_report_conn_err(check, errno, 0);
1287 ret = TCPCHK_EVAL_STOP;
1288 goto out;
1289 }
1290
1291 /* don't do anything until the connection is established */
1292 if (conn->flags & CO_FL_WAIT_XPRT) {
1293 if (conn->mux) {
1294 if (next && next->action == TCPCHK_ACT_SEND)
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001295 conn->mux->subscribe(check->cs, SUB_RETRY_SEND, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001296 else
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001297 conn->mux->subscribe(check->cs, SUB_RETRY_RECV, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001298 }
1299 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001300 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001301 goto out;
1302 }
1303
1304 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001305 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001306 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001307 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001308 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001309
1310 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1311 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1312
Christopher Faulet147b8c92021-04-10 09:00:38 +02001313 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001314 return ret;
1315}
1316
1317/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1318 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1319 * TCPCHK_EVAL_STOP if an error occurred.
1320 */
1321enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1322{
1323 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1324 struct tcpcheck_send *send = &rule->send;
1325 struct conn_stream *cs = check->cs;
1326 struct connection *conn = cs_conn(cs);
1327 struct buffer *tmp = NULL;
1328 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001329 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001330
Christopher Faulet147b8c92021-04-10 09:00:38 +02001331 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1332
Christopher Fauletb381a502020-11-25 13:47:00 +01001333 if (check->state & CHK_ST_OUT_ALLOC) {
1334 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001335 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001336 goto out;
1337 }
1338
1339 if (!check_get_buf(check, &check->bo)) {
1340 check->state |= CHK_ST_OUT_ALLOC;
1341 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001342 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001343 goto out;
1344 }
1345
Christopher Faulet39066c22020-11-25 13:34:51 +01001346 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001347 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001348 TRACE_DEVEL("Data still pending, try to send it now", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Christopher Faulet39066c22020-11-25 13:34:51 +01001349 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001350 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001351
Christopher Fauletb381a502020-11-25 13:47:00 +01001352 /* Always release input buffer when a new send is evaluated */
1353 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001354
1355 switch (send->type) {
1356 case TCPCHK_SEND_STRING:
1357 case TCPCHK_SEND_BINARY:
1358 if (istlen(send->data) >= b_size(&check->bo)) {
1359 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1360 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1361 tcpcheck_get_step_id(check, rule));
1362 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1363 ret = TCPCHK_EVAL_STOP;
1364 goto out;
1365 }
1366 b_putist(&check->bo, send->data);
1367 break;
1368 case TCPCHK_SEND_STRING_LF:
1369 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1370 if (!b_data(&check->bo))
1371 goto out;
1372 break;
1373 case TCPCHK_SEND_BINARY_LF: {
1374 int len = b_size(&check->bo);
1375
1376 tmp = alloc_trash_chunk();
1377 if (!tmp)
1378 goto error_lf;
1379 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1380 if (!b_data(tmp))
1381 goto out;
1382 tmp->area[tmp->data] = '\0';
1383 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1384 goto error_lf;
1385 check->bo.data = len;
1386 break;
1387 }
1388 case TCPCHK_SEND_HTTP: {
1389 struct htx_sl *sl;
1390 struct ist meth, uri, vsn, clen, body;
1391 unsigned int slflags = 0;
1392
1393 tmp = alloc_trash_chunk();
1394 if (!tmp)
1395 goto error_htx;
1396
1397 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1398 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1399 : http_known_methods[send->http.meth.meth]);
1400 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1401 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1402 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1403 }
1404 else
1405 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1406 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1407
1408 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1409 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1410 slflags |= HTX_SL_F_VER_11;
1411 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1412 if (!isttest(send->http.body))
1413 slflags |= HTX_SL_F_BODYLESS;
1414
1415 htx = htx_from_buf(&check->bo);
1416 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1417 if (!sl)
1418 goto error_htx;
1419 sl->info.req.meth = send->http.meth.meth;
1420 if (!http_update_host(htx, sl, uri))
1421 goto error_htx;
1422
1423 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1424 struct tcpcheck_http_hdr *hdr;
1425 struct ist hdr_value;
1426
1427 list_for_each_entry(hdr, &send->http.hdrs, list) {
1428 chunk_reset(tmp);
1429 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1430 if (!b_data(tmp))
1431 continue;
1432 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1433 if (!htx_add_header(htx, hdr->name, hdr_value))
1434 goto error_htx;
1435 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1436 if (!http_update_authority(htx, sl, hdr_value))
1437 goto error_htx;
1438 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001439 if (isteqi(hdr->name, ist("connection")))
1440 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001441 }
1442
1443 }
1444 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1445 chunk_reset(tmp);
1446 httpchk_build_status_header(check->server, tmp);
1447 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1448 goto error_htx;
1449 }
1450
1451
1452 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1453 chunk_reset(tmp);
1454 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1455 body = ist2(b_orig(tmp), b_data(tmp));
1456 }
1457 else
1458 body = send->http.body;
1459 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1460
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001461 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001462 !htx_add_header(htx, ist("Content-length"), clen))
1463 goto error_htx;
1464
1465
1466 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001467 (istlen(body) && !htx_add_data_atonce(htx, body)))
1468 goto error_htx;
1469
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001470 /* no more data are expected */
1471 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001472 htx_to_buf(htx, &check->bo);
1473 break;
1474 }
1475 case TCPCHK_SEND_UNDEF:
1476 /* Should never happen. */
1477 ret = TCPCHK_EVAL_STOP;
1478 goto out;
1479 };
1480
Christopher Faulet39066c22020-11-25 13:34:51 +01001481 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001482 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001483 if (conn->mux->snd_buf(cs, &check->bo,
1484 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1485 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1486 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001487 TRACE_DEVEL("connection error during send", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001488 goto out;
1489 }
1490 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001491 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Faulet897d6122021-12-17 17:28:35 +01001492 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001493 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001494 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001495 goto out;
1496 }
1497
1498 out:
1499 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001500 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1501 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001502
1503 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001504 return ret;
1505
1506 error_htx:
1507 if (htx) {
1508 htx_reset(htx);
1509 htx_to_buf(htx, &check->bo);
1510 }
1511 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1512 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001513 TRACE_ERROR("failed to build HTTP request", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001514 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1515 ret = TCPCHK_EVAL_STOP;
1516 goto out;
1517
1518 error_lf:
1519 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1520 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001521 TRACE_ERROR("failed to build log-format string", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001522 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1523 ret = TCPCHK_EVAL_STOP;
1524 goto out;
1525
1526}
1527
1528/* Try to receive data before evaluating a tcp-check expect rule. Returns
1529 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1530 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1531 * TCPCHK_EVAL_STOP if an error occurred.
1532 */
1533enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1534{
1535 struct conn_stream *cs = check->cs;
1536 struct connection *conn = cs_conn(cs);
1537 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1538 size_t max, read, cur_read = 0;
1539 int is_empty;
1540 int read_poll = MAX_READ_POLL_LOOPS;
1541
Christopher Faulet147b8c92021-04-10 09:00:38 +02001542 TRACE_ENTER(CHK_EV_RX_DATA, check);
1543
1544 if (check->wait_list.events & SUB_RETRY_RECV) {
1545 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001546 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001547 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001548
1549 if (cs->flags & CS_FL_EOS)
1550 goto end_recv;
1551
Christopher Faulet147b8c92021-04-10 09:00:38 +02001552 if (check->state & CHK_ST_IN_ALLOC) {
1553 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001554 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001555 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001556
1557 if (!check_get_buf(check, &check->bi)) {
1558 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001559 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001560 goto wait_more_data;
1561 }
1562
Willy Tarreau51cd5952020-06-05 12:25:38 +02001563 /* errors on the connection and the conn-stream were already checked */
1564
1565 /* prepare to detect if the mux needs more room */
1566 cs->flags &= ~CS_FL_WANT_ROOM;
1567
1568 while ((cs->flags & CS_FL_RCV_MORE) ||
1569 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1570 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1571 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1572 cur_read += read;
1573 if (!read ||
1574 (cs->flags & CS_FL_WANT_ROOM) ||
1575 (--read_poll <= 0) ||
1576 (read < max && read >= global.tune.recv_enough))
1577 break;
1578 }
1579
1580 end_recv:
1581 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1582 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1583 /* Report network errors only if we got no other data. Otherwise
1584 * we'll let the upper layers decide whether the response is OK
1585 * or not. It is very common that an RST sent by the server is
1586 * reported as an error just after the last data chunk.
1587 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001588 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001589 goto stop;
1590 }
1591 if (!cur_read) {
Christopher Fauletd16e7dd2021-10-20 13:53:38 +02001592 if (cs->flags & CS_FL_EOI) {
1593 /* If EOI is set, it means there is a response or an error */
1594 goto out;
1595 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001596 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1597 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001598 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001599 goto wait_more_data;
1600 }
1601 if (is_empty) {
1602 int status;
1603
1604 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1605 tcpcheck_get_step_id(check, rule));
1606 if (rule->comment)
1607 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1608
Christopher Faulet147b8c92021-04-10 09:00:38 +02001609 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001610 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1611 set_server_check_status(check, status, trash.area);
1612 goto stop;
1613 }
1614 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001615 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001616
1617 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001618 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1619 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001620
1621 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001622 return ret;
1623
1624 stop:
1625 ret = TCPCHK_EVAL_STOP;
1626 goto out;
1627
1628 wait_more_data:
1629 ret = TCPCHK_EVAL_WAIT;
1630 goto out;
1631}
1632
1633/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1634 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1635 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1636 * error occurred.
1637 */
1638enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1639{
1640 struct htx *htx = htxbuf(&check->bi);
1641 struct htx_sl *sl;
1642 struct htx_blk *blk;
1643 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1644 struct tcpcheck_expect *expect = &rule->expect;
1645 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1646 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1647 struct ist desc = IST_NULL;
1648 int i, match, inverse;
1649
Christopher Faulet147b8c92021-04-10 09:00:38 +02001650 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1651
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001652 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001653
1654 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001655 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001656 status = HCHK_STATUS_L7RSP;
1657 goto error;
1658 }
1659
1660 if (htx_is_empty(htx)) {
1661 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001662 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001663 status = HCHK_STATUS_L7RSP;
1664 goto error;
1665 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001666 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001667 goto wait_more_data;
1668 }
1669
1670 sl = http_get_stline(htx);
1671 check->code = sl->info.res.status;
1672
1673 if (check->server &&
1674 (check->server->proxy->options & PR_O_DISABLE404) &&
1675 (check->server->next_state != SRV_ST_STOPPED) &&
1676 (check->code == 404)) {
1677 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001678 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001679 goto out;
1680 }
1681
1682 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1683 /* Make GCC happy ; initialize match to a failure state. */
1684 match = inverse;
1685 status = expect->err_status;
1686
1687 switch (expect->type) {
1688 case TCPCHK_EXPECT_HTTP_STATUS:
1689 match = 0;
1690 for (i = 0; i < expect->codes.num; i++) {
1691 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1692 sl->info.res.status <= expect->codes.codes[i][1]) {
1693 match = 1;
1694 break;
1695 }
1696 }
1697
1698 /* Set status and description in case of error */
1699 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1700 if (LIST_ISEMPTY(&expect->onerror_fmt))
1701 desc = htx_sl_res_reason(sl);
1702 break;
1703 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1704 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1705
1706 /* Set status and description in case of error */
1707 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1708 if (LIST_ISEMPTY(&expect->onerror_fmt))
1709 desc = htx_sl_res_reason(sl);
1710 break;
1711
1712 case TCPCHK_EXPECT_HTTP_HEADER: {
1713 struct http_hdr_ctx ctx;
1714 struct ist npat, vpat, value;
1715 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1716
1717 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1718 nbuf = alloc_trash_chunk();
1719 if (!nbuf) {
1720 status = HCHK_STATUS_L7RSP;
1721 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001722 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001723 goto error;
1724 }
1725 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1726 if (!b_data(nbuf)) {
1727 status = HCHK_STATUS_L7RSP;
1728 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001729 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001730 goto error;
1731 }
1732 npat = ist2(b_orig(nbuf), b_data(nbuf));
1733 }
1734 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1735 npat = expect->hdr.name;
1736
1737 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1738 vbuf = alloc_trash_chunk();
1739 if (!vbuf) {
1740 status = HCHK_STATUS_L7RSP;
1741 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001742 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001743 goto error;
1744 }
1745 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1746 if (!b_data(vbuf)) {
1747 status = HCHK_STATUS_L7RSP;
1748 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001749 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001750 goto error;
1751 }
1752 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1753 }
1754 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1755 vpat = expect->hdr.value;
1756
1757 match = 0;
1758 ctx.blk = NULL;
1759 while (1) {
1760 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1761 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1762 if (!http_find_str_header(htx, npat, &ctx, full))
1763 goto end_of_match;
1764 break;
1765 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1766 if (!http_find_pfx_header(htx, npat, &ctx, full))
1767 goto end_of_match;
1768 break;
1769 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1770 if (!http_find_sfx_header(htx, npat, &ctx, full))
1771 goto end_of_match;
1772 break;
1773 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1774 if (!http_find_sub_header(htx, npat, &ctx, full))
1775 goto end_of_match;
1776 break;
1777 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1778 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1779 goto end_of_match;
1780 break;
1781 default:
1782 /* should never happen */
1783 goto end_of_match;
1784 }
1785
1786 /* A header has matched the name pattern, let's test its
1787 * value now (always defined from there). If there is no
1788 * value pattern, it is a good match.
1789 */
1790
1791 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1792 match = 1;
1793 goto end_of_match;
1794 }
1795
1796 value = ctx.value;
1797 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1798 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1799 if (isteq(value, vpat)) {
1800 match = 1;
1801 goto end_of_match;
1802 }
1803 break;
1804 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1805 if (istlen(value) < istlen(vpat))
1806 break;
1807 value = ist2(istptr(value), istlen(vpat));
1808 if (isteq(value, vpat)) {
1809 match = 1;
1810 goto end_of_match;
1811 }
1812 break;
1813 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1814 if (istlen(value) < istlen(vpat))
1815 break;
Tim Duesterhus4c8f75f2021-11-06 15:14:44 +01001816 value = ist2(istend(value) - istlen(vpat), istlen(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001817 if (isteq(value, vpat)) {
1818 match = 1;
1819 goto end_of_match;
1820 }
1821 break;
1822 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1823 if (isttest(istist(value, vpat))) {
1824 match = 1;
1825 goto end_of_match;
1826 }
1827 break;
1828 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1829 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1830 match = 1;
1831 goto end_of_match;
1832 }
1833 break;
1834 }
1835 }
1836
1837 end_of_match:
1838 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1839 if (LIST_ISEMPTY(&expect->onerror_fmt))
1840 desc = htx_sl_res_reason(sl);
1841 break;
1842 }
1843
1844 case TCPCHK_EXPECT_HTTP_BODY:
1845 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1846 case TCPCHK_EXPECT_HTTP_BODY_LF:
1847 match = 0;
1848 chunk_reset(&trash);
1849 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1850 enum htx_blk_type type = htx_get_blk_type(blk);
1851
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001852 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001853 break;
1854 if (type == HTX_BLK_DATA) {
1855 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1856 break;
1857 }
1858 }
1859
1860 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001861 if (!last_read) {
1862 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001863 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001864 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001865 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1866 if (LIST_ISEMPTY(&expect->onerror_fmt))
1867 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001868 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001869 goto error;
1870 }
1871
1872 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1873 tmp = alloc_trash_chunk();
1874 if (!tmp) {
1875 status = HCHK_STATUS_L7RSP;
1876 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001877 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001878 goto error;
1879 }
1880 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1881 if (!b_data(tmp)) {
1882 status = HCHK_STATUS_L7RSP;
1883 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001884 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001885 goto error;
1886 }
1887 }
1888
1889 if (!last_read &&
1890 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1891 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1892 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1893 ret = TCPCHK_EVAL_WAIT;
1894 goto out;
1895 }
1896
1897 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1898 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1899 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1900 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1901 else
1902 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1903
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001904 /* Wait for more data on mismatch only if no minimum is defined (-1),
1905 * otherwise the absence of match is already conclusive.
1906 */
1907 if (!match && !last_read && (expect->min_recv == -1)) {
1908 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001909 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001910 goto out;
1911 }
1912
Willy Tarreau51cd5952020-06-05 12:25:38 +02001913 /* Set status and description in case of error */
1914 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1915 if (LIST_ISEMPTY(&expect->onerror_fmt))
1916 desc = (inverse
1917 ? ist("HTTP check matched unwanted content")
1918 : ist("HTTP content check did not match"));
1919 break;
1920
1921
1922 default:
1923 /* should never happen */
1924 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1925 goto error;
1926 }
1927
Christopher Faulet147b8c92021-04-10 09:00:38 +02001928 if (!(match ^ inverse)) {
1929 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001930 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001931 }
1932
1933 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001934
1935 out:
1936 free_trash_chunk(tmp);
1937 free_trash_chunk(nbuf);
1938 free_trash_chunk(vbuf);
1939 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001940 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001941 return ret;
1942
1943 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001944 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001945 ret = TCPCHK_EVAL_STOP;
1946 msg = alloc_trash_chunk();
1947 if (msg)
1948 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1949 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1950 goto out;
1951
1952 wait_more_data:
1953 ret = TCPCHK_EVAL_WAIT;
1954 goto out;
1955}
1956
1957/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1958 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1959 * if an error occurred.
1960 */
1961enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1962{
1963 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1964 struct tcpcheck_expect *expect = &rule->expect;
1965 struct buffer *msg = NULL, *tmp = NULL;
1966 struct ist desc = IST_NULL;
1967 enum healthcheck_status status;
1968 int match, inverse;
1969
Christopher Faulet147b8c92021-04-10 09:00:38 +02001970 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1971
Willy Tarreau51cd5952020-06-05 12:25:38 +02001972 last_read |= b_full(&check->bi);
1973
1974 /* The current expect might need more data than the previous one, check again
1975 * that the minimum amount data required to match is respected.
1976 */
1977 if (!last_read) {
1978 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1979 (b_data(&check->bi) < istlen(expect->data))) {
1980 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001981 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001982 goto out;
1983 }
1984 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1985 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001986 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001987 goto out;
1988 }
1989 }
1990
1991 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1992 /* Make GCC happy ; initialize match to a failure state. */
1993 match = inverse;
1994 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1995
1996 switch (expect->type) {
1997 case TCPCHK_EXPECT_STRING:
1998 case TCPCHK_EXPECT_BINARY:
1999 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2000 break;
2001 case TCPCHK_EXPECT_STRING_REGEX:
2002 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2003 break;
2004
2005 case TCPCHK_EXPECT_BINARY_REGEX:
2006 chunk_reset(&trash);
2007 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2008 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2009 break;
2010
2011 case TCPCHK_EXPECT_STRING_LF:
2012 case TCPCHK_EXPECT_BINARY_LF:
2013 match = 0;
2014 tmp = alloc_trash_chunk();
2015 if (!tmp) {
2016 status = HCHK_STATUS_L7RSP;
2017 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002018 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002019 goto error;
2020 }
2021 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2022 if (!b_data(tmp)) {
2023 status = HCHK_STATUS_L7RSP;
2024 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002025 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002026 goto error;
2027 }
2028 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2029 int len = tmp->data;
2030 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2031 status = HCHK_STATUS_L7RSP;
2032 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002033 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002034 goto error;
2035 }
2036 tmp->data = len;
2037 }
2038 if (b_data(&check->bi) < tmp->data) {
2039 if (!last_read) {
2040 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002041 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002042 goto out;
2043 }
2044 break;
2045 }
2046 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2047 break;
2048
2049 case TCPCHK_EXPECT_CUSTOM:
2050 if (expect->custom)
2051 ret = expect->custom(check, rule, last_read);
2052 goto out;
2053 default:
2054 /* Should never happen. */
2055 ret = TCPCHK_EVAL_STOP;
2056 goto out;
2057 }
2058
2059
2060 /* Wait for more data on mismatch only if no minimum is defined (-1),
2061 * otherwise the absence of match is already conclusive.
2062 */
2063 if (!match && !last_read && (expect->min_recv == -1)) {
2064 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002065 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002066 goto out;
2067 }
2068
2069 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002070 if (match ^ inverse) {
2071 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002072 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002073 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002074
2075 error:
2076 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002077 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002078 ret = TCPCHK_EVAL_STOP;
2079 msg = alloc_trash_chunk();
2080 if (msg)
2081 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2082 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2083 free_trash_chunk(msg);
2084
2085 out:
2086 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002087 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002088 return ret;
2089}
2090
2091/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2092 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2093 * waits.
2094 */
2095enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2096{
2097 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2098 struct act_rule *act_rule;
2099 enum act_return act_ret;
2100
2101 act_rule =rule->action_kw.rule;
2102 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2103 if (act_ret != ACT_RET_CONT) {
2104 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2105 tcpcheck_get_step_id(check, rule));
2106 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2107 ret = TCPCHK_EVAL_STOP;
2108 }
2109
2110 return ret;
2111}
2112
2113/* Executes a tcp-check ruleset. Note that this is called both from the
2114 * connection's wake() callback and from the check scheduling task. It returns
2115 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2116 * presenting the risk of an fd replacement.
2117 *
2118 * Please do NOT place any return statement in this function and only leave
2119 * via the out_end_tcpcheck label after setting retcode.
2120 */
2121int tcpcheck_main(struct check *check)
2122{
2123 struct tcpcheck_rule *rule;
2124 struct conn_stream *cs = check->cs;
2125 struct connection *conn = cs_conn(cs);
2126 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002127 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002128 enum tcpcheck_eval_ret eval_ret;
2129
2130 /* here, we know that the check is complete or that it failed */
2131 if (check->result != CHK_RES_UNKNOWN)
2132 goto out;
2133
Christopher Faulet147b8c92021-04-10 09:00:38 +02002134 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2135
Willy Tarreau51cd5952020-06-05 12:25:38 +02002136 /* Note: the conn-stream and the connection may only be undefined before
2137 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002138 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002139 */
2140
2141 /* 1- check for connection error, if any */
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002142 if ((conn && conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002143 goto out_end_tcpcheck;
2144
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002145 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002146 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002147 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002148 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002149 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2150 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002151
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002152 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002153 * tcp-check variables */
2154 else {
2155 struct tcpcheck_var *var;
2156
2157 /* First evaluation, create a session */
2158 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2159 if (!check->sess) {
2160 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002161 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002162 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2163 goto out_end_tcpcheck;
2164 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002165 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002166 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2167
2168 /* Preset tcp-check variables */
2169 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2170 struct sample smp;
2171
2172 memset(&smp, 0, sizeof(smp));
2173 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2174 smp.data = var->data;
2175 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2176 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002177 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002178 }
2179
2180 /* Now evaluate the tcp-check rules */
2181
2182 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2183 check->code = 0;
2184 switch (rule->action) {
2185 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002186 /* Not the first connection, release it first */
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002187 if (cs_conn(cs) && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002188 check->state |= CHK_ST_CLOSE_CONN;
2189 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002190 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002191
2192 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002193
2194 /* We are still waiting the connection gets closed */
2195 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002196 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002197 eval_ret = TCPCHK_EVAL_WAIT;
2198 break;
2199 }
2200
Christopher Faulet147b8c92021-04-10 09:00:38 +02002201 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002202 eval_ret = tcpcheck_eval_connect(check, rule);
2203
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002204 /* Refresh connection */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002205 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002206 last_read = 0;
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002207 must_read = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002208 break;
2209 case TCPCHK_ACT_SEND:
2210 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002211 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002212 eval_ret = tcpcheck_eval_send(check, rule);
2213 must_read = 1;
2214 break;
2215 case TCPCHK_ACT_EXPECT:
2216 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002217 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002218 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002219 eval_ret = tcpcheck_eval_recv(check, rule);
2220 if (eval_ret == TCPCHK_EVAL_STOP)
2221 goto out_end_tcpcheck;
2222 else if (eval_ret == TCPCHK_EVAL_WAIT)
2223 goto out;
2224 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2225 must_read = 0;
2226 }
2227
2228 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2229 ? tcpcheck_eval_expect_http(check, rule, last_read)
2230 : tcpcheck_eval_expect(check, rule, last_read));
2231
2232 if (eval_ret == TCPCHK_EVAL_WAIT) {
2233 check->current_step = rule->expect.head;
2234 if (!(check->wait_list.events & SUB_RETRY_RECV))
2235 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2236 }
2237 break;
2238 case TCPCHK_ACT_ACTION_KW:
2239 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002240 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002241 eval_ret = tcpcheck_eval_action_kw(check, rule);
2242 break;
2243 default:
2244 /* Otherwise, just go to the next one and don't update
2245 * the current step
2246 */
2247 eval_ret = TCPCHK_EVAL_CONTINUE;
2248 break;
2249 }
2250
2251 switch (eval_ret) {
2252 case TCPCHK_EVAL_CONTINUE:
2253 break;
2254 case TCPCHK_EVAL_WAIT:
2255 goto out;
2256 case TCPCHK_EVAL_STOP:
2257 goto out_end_tcpcheck;
2258 }
2259 }
2260
2261 /* All rules was evaluated */
2262 if (check->current_step) {
2263 rule = check->current_step;
2264
Christopher Faulet147b8c92021-04-10 09:00:38 +02002265 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2266
Willy Tarreau51cd5952020-06-05 12:25:38 +02002267 if (rule->action == TCPCHK_ACT_EXPECT) {
2268 struct buffer *msg;
2269 enum healthcheck_status status;
2270
2271 if (check->server &&
2272 (check->server->proxy->options & PR_O_DISABLE404) &&
2273 (check->server->next_state != SRV_ST_STOPPED) &&
2274 (check->code == 404)) {
2275 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002276 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002277 goto out_end_tcpcheck;
2278 }
2279
2280 msg = alloc_trash_chunk();
2281 if (msg)
2282 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2283 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2284 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2285 free_trash_chunk(msg);
2286 }
2287 else if (rule->action == TCPCHK_ACT_CONNECT) {
2288 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2289 enum healthcheck_status status = HCHK_STATUS_L4OK;
2290#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002291 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002292 status = HCHK_STATUS_L6OK;
2293#endif
2294 set_server_check_status(check, status, msg);
2295 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002296 else
2297 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002298 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002299 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002300 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002301 }
2302 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002303
2304 out_end_tcpcheck:
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002305 if ((conn && conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002306 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002307 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002308 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002309
Christopher Fauletb381a502020-11-25 13:47:00 +01002310 /* the tcpcheck is finished, release in/out buffer now */
2311 check_release_buf(check, &check->bi);
2312 check_release_buf(check, &check->bo);
2313
Willy Tarreau51cd5952020-06-05 12:25:38 +02002314 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002315 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002316 return retcode;
2317}
2318
2319
2320/**************************************************************************/
2321/******************* Internals to parse tcp-check rules *******************/
2322/**************************************************************************/
2323struct action_kw_list tcp_check_keywords = {
2324 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2325};
2326
2327/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2328 * returned on error.
2329 */
2330struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2331 struct list *rules, struct action_kw *kw,
2332 const char *file, int line, char **errmsg)
2333{
2334 struct tcpcheck_rule *chk = NULL;
2335 struct act_rule *actrule = NULL;
2336
Willy Tarreaud535f802021-10-11 08:49:26 +02002337 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002338 if (!actrule) {
2339 memprintf(errmsg, "out of memory");
2340 goto error;
2341 }
2342 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002343
2344 cur_arg++;
2345 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2346 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2347 goto error;
2348 }
2349
2350 chk = calloc(1, sizeof(*chk));
2351 if (!chk) {
2352 memprintf(errmsg, "out of memory");
2353 goto error;
2354 }
2355 chk->action = TCPCHK_ACT_ACTION_KW;
2356 chk->action_kw.rule = actrule;
2357 return chk;
2358
2359 error:
2360 free(actrule);
2361 return NULL;
2362}
2363
2364/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2365 * returned on error.
2366 */
2367struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2368 const char *file, int line, char **errmsg)
2369{
2370 struct tcpcheck_rule *chk = NULL;
2371 struct sockaddr_storage *sk = NULL;
2372 char *comment = NULL, *sni = NULL, *alpn = NULL;
2373 struct sample_expr *port_expr = NULL;
2374 const struct mux_proto_list *mux_proto = NULL;
2375 unsigned short conn_opts = 0;
2376 long port = 0;
2377 int alpn_len = 0;
2378
2379 list_for_each_entry(chk, rules, list) {
2380 if (chk->action == TCPCHK_ACT_CONNECT)
2381 break;
2382 if (chk->action == TCPCHK_ACT_COMMENT ||
2383 chk->action == TCPCHK_ACT_ACTION_KW ||
2384 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2385 continue;
2386
2387 memprintf(errmsg, "first step MUST also be a 'connect', "
2388 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2389 "when there is a 'connect' step in the tcp-check ruleset");
2390 goto error;
2391 }
2392
2393 cur_arg++;
2394 while (*(args[cur_arg])) {
2395 if (strcmp(args[cur_arg], "default") == 0)
2396 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2397 else if (strcmp(args[cur_arg], "addr") == 0) {
2398 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002399
2400 if (!*(args[cur_arg+1])) {
2401 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2402 goto error;
2403 }
2404
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002405 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2406 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002407 if (!sk) {
2408 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2409 goto error;
2410 }
2411
Willy Tarreau51cd5952020-06-05 12:25:38 +02002412 cur_arg++;
2413 }
2414 else if (strcmp(args[cur_arg], "port") == 0) {
2415 const char *p, *end;
2416
2417 if (!*(args[cur_arg+1])) {
2418 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2419 goto error;
2420 }
2421 cur_arg++;
2422
2423 port = 0;
2424 release_sample_expr(port_expr);
2425 p = args[cur_arg]; end = p + strlen(p);
2426 port = read_uint(&p, end);
2427 if (p != end) {
2428 int idx = 0;
2429
2430 px->conf.args.ctx = ARGC_SRV;
2431 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002432 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002433
2434 if (!port_expr) {
2435 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2436 goto error;
2437 }
2438 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2439 memprintf(errmsg, "error detected while parsing port expression : "
2440 " fetch method '%s' extracts information from '%s', "
2441 "none of which is available here.\n",
2442 args[cur_arg], sample_src_names(port_expr->fetch->use));
2443 goto error;
2444 }
2445 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2446 }
2447 else if (port > 65535 || port < 1) {
2448 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2449 args[cur_arg]);
2450 goto error;
2451 }
2452 }
2453 else if (strcmp(args[cur_arg], "proto") == 0) {
2454 if (!*(args[cur_arg+1])) {
2455 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2456 goto error;
2457 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002458 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002459 if (!mux_proto) {
2460 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2461 goto error;
2462 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002463
2464 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2465 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2466 goto error;
2467 }
2468 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2469 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2470 goto error;
2471 }
2472
Willy Tarreau51cd5952020-06-05 12:25:38 +02002473 cur_arg++;
2474 }
2475 else if (strcmp(args[cur_arg], "comment") == 0) {
2476 if (!*(args[cur_arg+1])) {
2477 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2478 goto error;
2479 }
2480 cur_arg++;
2481 free(comment);
2482 comment = strdup(args[cur_arg]);
2483 if (!comment) {
2484 memprintf(errmsg, "out of memory");
2485 goto error;
2486 }
2487 }
2488 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2489 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2490 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2491 conn_opts |= TCPCHK_OPT_SOCKS4;
2492 else if (strcmp(args[cur_arg], "linger") == 0)
2493 conn_opts |= TCPCHK_OPT_LINGER;
2494#ifdef USE_OPENSSL
2495 else if (strcmp(args[cur_arg], "ssl") == 0) {
2496 px->options |= PR_O_TCPCHK_SSL;
2497 conn_opts |= TCPCHK_OPT_SSL;
2498 }
2499 else if (strcmp(args[cur_arg], "sni") == 0) {
2500 if (!*(args[cur_arg+1])) {
2501 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2502 goto error;
2503 }
2504 cur_arg++;
2505 free(sni);
2506 sni = strdup(args[cur_arg]);
2507 if (!sni) {
2508 memprintf(errmsg, "out of memory");
2509 goto error;
2510 }
2511 }
2512 else if (strcmp(args[cur_arg], "alpn") == 0) {
2513#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2514 free(alpn);
2515 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2516 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2517 goto error;
2518 }
2519 cur_arg++;
2520#else
2521 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2522 goto error;
2523#endif
2524 }
2525#endif /* USE_OPENSSL */
2526
2527 else {
2528 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2529#ifdef USE_OPENSSL
2530 ", 'ssl', 'sni', 'alpn'"
2531#endif /* USE_OPENSSL */
2532 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2533 args[cur_arg]);
2534 goto error;
2535 }
2536 cur_arg++;
2537 }
2538
2539 chk = calloc(1, sizeof(*chk));
2540 if (!chk) {
2541 memprintf(errmsg, "out of memory");
2542 goto error;
2543 }
2544 chk->action = TCPCHK_ACT_CONNECT;
2545 chk->comment = comment;
2546 chk->connect.port = port;
2547 chk->connect.options = conn_opts;
2548 chk->connect.sni = sni;
2549 chk->connect.alpn = alpn;
2550 chk->connect.alpn_len= alpn_len;
2551 chk->connect.port_expr= port_expr;
2552 chk->connect.mux_proto= mux_proto;
2553 if (sk)
2554 chk->connect.addr = *sk;
2555 return chk;
2556
2557 error:
2558 free(alpn);
2559 free(sni);
2560 free(comment);
2561 release_sample_expr(port_expr);
2562 return NULL;
2563}
2564
2565/* Parses and creates a tcp-check send rule. NULL is returned on error */
2566struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2567 const char *file, int line, char **errmsg)
2568{
2569 struct tcpcheck_rule *chk = NULL;
2570 char *comment = NULL, *data = NULL;
2571 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2572
2573 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2574 type = TCPCHK_SEND_BINARY_LF;
2575 else if (strcmp(args[cur_arg], "send-binary") == 0)
2576 type = TCPCHK_SEND_BINARY;
2577 else if (strcmp(args[cur_arg], "send-lf") == 0)
2578 type = TCPCHK_SEND_STRING_LF;
2579 else if (strcmp(args[cur_arg], "send") == 0)
2580 type = TCPCHK_SEND_STRING;
2581
2582 if (!*(args[cur_arg+1])) {
2583 memprintf(errmsg, "'%s' expects a %s as argument",
2584 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2585 goto error;
2586 }
2587
2588 data = args[cur_arg+1];
2589
2590 cur_arg += 2;
2591 while (*(args[cur_arg])) {
2592 if (strcmp(args[cur_arg], "comment") == 0) {
2593 if (!*(args[cur_arg+1])) {
2594 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2595 goto error;
2596 }
2597 cur_arg++;
2598 free(comment);
2599 comment = strdup(args[cur_arg]);
2600 if (!comment) {
2601 memprintf(errmsg, "out of memory");
2602 goto error;
2603 }
2604 }
2605 else {
2606 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2607 args[cur_arg]);
2608 goto error;
2609 }
2610 cur_arg++;
2611 }
2612
2613 chk = calloc(1, sizeof(*chk));
2614 if (!chk) {
2615 memprintf(errmsg, "out of memory");
2616 goto error;
2617 }
2618 chk->action = TCPCHK_ACT_SEND;
2619 chk->comment = comment;
2620 chk->send.type = type;
2621
2622 switch (chk->send.type) {
2623 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002624 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002625 if (!isttest(chk->send.data)) {
2626 memprintf(errmsg, "out of memory");
2627 goto error;
2628 }
2629 break;
2630 case TCPCHK_SEND_BINARY: {
2631 int len = chk->send.data.len;
2632 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2633 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2634 goto error;
2635 }
2636 chk->send.data.len = len;
2637 break;
2638 }
2639 case TCPCHK_SEND_STRING_LF:
2640 case TCPCHK_SEND_BINARY_LF:
2641 LIST_INIT(&chk->send.fmt);
2642 px->conf.args.ctx = ARGC_SRV;
2643 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2644 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2645 goto error;
2646 }
2647 break;
2648 case TCPCHK_SEND_HTTP:
2649 case TCPCHK_SEND_UNDEF:
2650 goto error;
2651 }
2652
2653 return chk;
2654
2655 error:
2656 free(chk);
2657 free(comment);
2658 return NULL;
2659}
2660
2661/* Parses and creates a http-check send rule. NULL is returned on error */
2662struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2663 const char *file, int line, char **errmsg)
2664{
2665 struct tcpcheck_rule *chk = NULL;
2666 struct tcpcheck_http_hdr *hdr = NULL;
2667 struct http_hdr hdrs[global.tune.max_http_hdr];
2668 char *meth = NULL, *uri = NULL, *vsn = NULL;
2669 char *body = NULL, *comment = NULL;
2670 unsigned int flags = 0;
2671 int i = 0, host_hdr = -1;
2672
2673 cur_arg++;
2674 while (*(args[cur_arg])) {
2675 if (strcmp(args[cur_arg], "meth") == 0) {
2676 if (!*(args[cur_arg+1])) {
2677 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2678 goto error;
2679 }
2680 cur_arg++;
2681 meth = args[cur_arg];
2682 }
2683 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2684 if (!*(args[cur_arg+1])) {
2685 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2686 goto error;
2687 }
2688 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2689 if (strcmp(args[cur_arg], "uri-lf") == 0)
2690 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2691 cur_arg++;
2692 uri = args[cur_arg];
2693 }
2694 else if (strcmp(args[cur_arg], "ver") == 0) {
2695 if (!*(args[cur_arg+1])) {
2696 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2697 goto error;
2698 }
2699 cur_arg++;
2700 vsn = args[cur_arg];
2701 }
2702 else if (strcmp(args[cur_arg], "hdr") == 0) {
2703 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2704 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2705 goto error;
2706 }
2707
2708 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2709 if (host_hdr >= 0) {
2710 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2711 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2712 goto error;
2713 }
2714 host_hdr = i;
2715 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002716 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002717 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2718 goto skip_hdr;
2719
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002720 hdrs[i].n = ist(args[cur_arg + 1]);
2721 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002722 i++;
2723 skip_hdr:
2724 cur_arg += 2;
2725 }
2726 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2727 if (!*(args[cur_arg+1])) {
2728 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2729 goto error;
2730 }
2731 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2732 if (strcmp(args[cur_arg], "body-lf") == 0)
2733 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2734 cur_arg++;
2735 body = args[cur_arg];
2736 }
2737 else if (strcmp(args[cur_arg], "comment") == 0) {
2738 if (!*(args[cur_arg+1])) {
2739 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2740 goto error;
2741 }
2742 cur_arg++;
2743 free(comment);
2744 comment = strdup(args[cur_arg]);
2745 if (!comment) {
2746 memprintf(errmsg, "out of memory");
2747 goto error;
2748 }
2749 }
2750 else {
2751 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2752 " but got '%s' as argument.", args[cur_arg]);
2753 goto error;
2754 }
2755 cur_arg++;
2756 }
2757
2758 hdrs[i].n = hdrs[i].v = IST_NULL;
2759
2760 chk = calloc(1, sizeof(*chk));
2761 if (!chk) {
2762 memprintf(errmsg, "out of memory");
2763 goto error;
2764 }
2765 chk->action = TCPCHK_ACT_SEND;
2766 chk->comment = comment; comment = NULL;
2767 chk->send.type = TCPCHK_SEND_HTTP;
2768 chk->send.http.flags = flags;
2769 LIST_INIT(&chk->send.http.hdrs);
2770
2771 if (meth) {
2772 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2773 chk->send.http.meth.str.area = strdup(meth);
2774 chk->send.http.meth.str.data = strlen(meth);
2775 if (!chk->send.http.meth.str.area) {
2776 memprintf(errmsg, "out of memory");
2777 goto error;
2778 }
2779 }
2780 if (uri) {
2781 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2782 LIST_INIT(&chk->send.http.uri_fmt);
2783 px->conf.args.ctx = ARGC_SRV;
2784 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2785 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2786 goto error;
2787 }
2788 }
2789 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002790 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002791 if (!isttest(chk->send.http.uri)) {
2792 memprintf(errmsg, "out of memory");
2793 goto error;
2794 }
2795 }
2796 }
2797 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002798 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002799 if (!isttest(chk->send.http.vsn)) {
2800 memprintf(errmsg, "out of memory");
2801 goto error;
2802 }
2803 }
2804 for (i = 0; istlen(hdrs[i].n); i++) {
2805 hdr = calloc(1, sizeof(*hdr));
2806 if (!hdr) {
2807 memprintf(errmsg, "out of memory");
2808 goto error;
2809 }
2810 LIST_INIT(&hdr->value);
2811 hdr->name = istdup(hdrs[i].n);
2812 if (!isttest(hdr->name)) {
2813 memprintf(errmsg, "out of memory");
2814 goto error;
2815 }
2816
2817 ist0(hdrs[i].v);
2818 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2819 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002820 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002821 hdr = NULL;
2822 }
2823
2824 if (body) {
2825 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2826 LIST_INIT(&chk->send.http.body_fmt);
2827 px->conf.args.ctx = ARGC_SRV;
2828 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2829 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2830 goto error;
2831 }
2832 }
2833 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002834 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002835 if (!isttest(chk->send.http.body)) {
2836 memprintf(errmsg, "out of memory");
2837 goto error;
2838 }
2839 }
2840 }
2841
2842 return chk;
2843
2844 error:
2845 free_tcpcheck_http_hdr(hdr);
2846 free_tcpcheck(chk, 0);
2847 free(comment);
2848 return NULL;
2849}
2850
2851/* Parses and creates a http-check comment rule. NULL is returned on error */
2852struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2853 const char *file, int line, char **errmsg)
2854{
2855 struct tcpcheck_rule *chk = NULL;
2856 char *comment = NULL;
2857
2858 if (!*(args[cur_arg+1])) {
2859 memprintf(errmsg, "expects a string as argument");
2860 goto error;
2861 }
2862 cur_arg++;
2863 comment = strdup(args[cur_arg]);
2864 if (!comment) {
2865 memprintf(errmsg, "out of memory");
2866 goto error;
2867 }
2868
2869 chk = calloc(1, sizeof(*chk));
2870 if (!chk) {
2871 memprintf(errmsg, "out of memory");
2872 goto error;
2873 }
2874 chk->action = TCPCHK_ACT_COMMENT;
2875 chk->comment = comment;
2876 return chk;
2877
2878 error:
2879 free(comment);
2880 return NULL;
2881}
2882
2883/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2884 * on error. <proto> is set to the right protocol flags (covered by the
2885 * TCPCHK_RULES_PROTO_CHK mask).
2886 */
2887struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2888 struct list *rules, unsigned int proto,
2889 const char *file, int line, char **errmsg)
2890{
2891 struct tcpcheck_rule *prev_check, *chk = NULL;
2892 struct sample_expr *status_expr = NULL;
2893 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2894 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2895 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2896 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2897 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2898 unsigned int flags = 0;
2899 long min_recv = -1;
2900 int inverse = 0;
2901
2902 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2903 if (!*(args[cur_arg+1])) {
2904 memprintf(errmsg, "expects at least a matching pattern as arguments");
2905 goto error;
2906 }
2907
2908 cur_arg++;
2909 while (*(args[cur_arg])) {
2910 int in_pattern = 0;
2911
2912 rescan:
2913 if (strcmp(args[cur_arg], "min-recv") == 0) {
2914 if (in_pattern) {
2915 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2916 goto error;
2917 }
2918 if (!*(args[cur_arg+1])) {
2919 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2920 goto error;
2921 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002922 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002923 cur_arg++;
2924 min_recv = atol(args[cur_arg]);
2925 if (min_recv < -1 || min_recv > INT_MAX) {
2926 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2927 goto error;
2928 }
2929 }
2930 else if (*(args[cur_arg]) == '!') {
2931 in_pattern = 1;
2932 while (*(args[cur_arg]) == '!') {
2933 inverse = !inverse;
2934 args[cur_arg]++;
2935 }
2936 if (!*(args[cur_arg]))
2937 cur_arg++;
2938 goto rescan;
2939 }
2940 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2941 if (type != TCPCHK_EXPECT_UNDEF) {
2942 memprintf(errmsg, "only on pattern expected");
2943 goto error;
2944 }
2945 if (proto != TCPCHK_RULES_HTTP_CHK)
2946 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2947 else
2948 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2949
2950 if (!*(args[cur_arg+1])) {
2951 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2952 goto error;
2953 }
2954 cur_arg++;
2955 pattern = args[cur_arg];
2956 }
2957 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2958 if (proto == TCPCHK_RULES_HTTP_CHK)
2959 goto bad_http_kw;
2960 if (type != TCPCHK_EXPECT_UNDEF) {
2961 memprintf(errmsg, "only on pattern expected");
2962 goto error;
2963 }
2964 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2965
2966 if (!*(args[cur_arg+1])) {
2967 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2968 goto error;
2969 }
2970 cur_arg++;
2971 pattern = args[cur_arg];
2972 }
2973 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2974 if (type != TCPCHK_EXPECT_UNDEF) {
2975 memprintf(errmsg, "only on pattern expected");
2976 goto error;
2977 }
2978 if (proto != TCPCHK_RULES_HTTP_CHK)
2979 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2980 else {
2981 if (*(args[cur_arg]) != 's')
2982 goto bad_http_kw;
2983 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2984 }
2985
2986 if (!*(args[cur_arg+1])) {
2987 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2988 goto error;
2989 }
2990 cur_arg++;
2991 pattern = args[cur_arg];
2992 }
2993 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
2994 if (proto != TCPCHK_RULES_HTTP_CHK)
2995 goto bad_tcp_kw;
2996 if (type != TCPCHK_EXPECT_UNDEF) {
2997 memprintf(errmsg, "only on pattern expected");
2998 goto error;
2999 }
3000 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3001
3002 if (!*(args[cur_arg+1])) {
3003 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3004 goto error;
3005 }
3006 cur_arg++;
3007 pattern = args[cur_arg];
3008 }
3009 else if (strcmp(args[cur_arg], "custom") == 0) {
3010 if (in_pattern) {
3011 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3012 goto error;
3013 }
3014 if (type != TCPCHK_EXPECT_UNDEF) {
3015 memprintf(errmsg, "only on pattern expected");
3016 goto error;
3017 }
3018 type = TCPCHK_EXPECT_CUSTOM;
3019 }
3020 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3021 int orig_arg = cur_arg;
3022
3023 if (proto != TCPCHK_RULES_HTTP_CHK)
3024 goto bad_tcp_kw;
3025 if (type != TCPCHK_EXPECT_UNDEF) {
3026 memprintf(errmsg, "only on pattern expected");
3027 goto error;
3028 }
3029 type = TCPCHK_EXPECT_HTTP_HEADER;
3030
3031 if (strcmp(args[cur_arg], "fhdr") == 0)
3032 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3033
3034 /* Parse the name pattern, mandatory */
3035 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3036 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3037 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3038 args[orig_arg]);
3039 goto error;
3040 }
3041
3042 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3043 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3044
3045 cur_arg += 2;
3046 if (strcmp(args[cur_arg], "-m") == 0) {
3047 if (!*(args[cur_arg+1])) {
3048 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3049 args[orig_arg], args[cur_arg]);
3050 goto error;
3051 }
3052 if (strcmp(args[cur_arg+1], "str") == 0)
3053 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3054 else if (strcmp(args[cur_arg+1], "beg") == 0)
3055 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3056 else if (strcmp(args[cur_arg+1], "end") == 0)
3057 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3058 else if (strcmp(args[cur_arg+1], "sub") == 0)
3059 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3060 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3061 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3062 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3063 args[orig_arg]);
3064 goto error;
3065 }
3066 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3067 }
3068 else {
3069 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3070 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3071 goto error;
3072 }
3073 cur_arg += 2;
3074 }
3075 else
3076 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3077 npat = args[cur_arg];
3078
3079 if (!*(args[cur_arg+1]) ||
3080 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3081 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3082 goto next;
3083 }
3084 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3085 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3086
3087 /* Parse the value pattern, optional */
3088 if (strcmp(args[cur_arg+2], "-m") == 0) {
3089 cur_arg += 2;
3090 if (!*(args[cur_arg+1])) {
3091 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3092 args[orig_arg], args[cur_arg]);
3093 goto error;
3094 }
3095 if (strcmp(args[cur_arg+1], "str") == 0)
3096 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3097 else if (strcmp(args[cur_arg+1], "beg") == 0)
3098 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3099 else if (strcmp(args[cur_arg+1], "end") == 0)
3100 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3101 else if (strcmp(args[cur_arg+1], "sub") == 0)
3102 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3103 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3104 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3105 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3106 args[orig_arg]);
3107 goto error;
3108 }
3109 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3110 }
3111 else {
3112 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3113 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3114 goto error;
3115 }
3116 }
3117 else
3118 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3119
3120 if (!*(args[cur_arg+2])) {
3121 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3122 goto error;
3123 }
3124 vpat = args[cur_arg+2];
3125 cur_arg += 2;
3126 }
3127 else if (strcmp(args[cur_arg], "comment") == 0) {
3128 if (in_pattern) {
3129 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3130 goto error;
3131 }
3132 if (!*(args[cur_arg+1])) {
3133 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3134 goto error;
3135 }
3136 cur_arg++;
3137 free(comment);
3138 comment = strdup(args[cur_arg]);
3139 if (!comment) {
3140 memprintf(errmsg, "out of memory");
3141 goto error;
3142 }
3143 }
3144 else if (strcmp(args[cur_arg], "on-success") == 0) {
3145 if (in_pattern) {
3146 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3147 goto error;
3148 }
3149 if (!*(args[cur_arg+1])) {
3150 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3151 goto error;
3152 }
3153 cur_arg++;
3154 on_success_msg = args[cur_arg];
3155 }
3156 else if (strcmp(args[cur_arg], "on-error") == 0) {
3157 if (in_pattern) {
3158 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3159 goto error;
3160 }
3161 if (!*(args[cur_arg+1])) {
3162 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3163 goto error;
3164 }
3165 cur_arg++;
3166 on_error_msg = args[cur_arg];
3167 }
3168 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3169 if (in_pattern) {
3170 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3171 goto error;
3172 }
3173 if (!*(args[cur_arg+1])) {
3174 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3175 goto error;
3176 }
3177 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3178 ok_st = HCHK_STATUS_L7OKD;
3179 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3180 ok_st = HCHK_STATUS_L7OKCD;
3181 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3182 ok_st = HCHK_STATUS_L6OK;
3183 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3184 ok_st = HCHK_STATUS_L4OK;
3185 else {
3186 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3187 args[cur_arg], args[cur_arg+1]);
3188 goto error;
3189 }
3190 cur_arg++;
3191 }
3192 else if (strcmp(args[cur_arg], "error-status") == 0) {
3193 if (in_pattern) {
3194 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3195 goto error;
3196 }
3197 if (!*(args[cur_arg+1])) {
3198 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3199 goto error;
3200 }
3201 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3202 err_st = HCHK_STATUS_L7RSP;
3203 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3204 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003205 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3206 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003207 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3208 err_st = HCHK_STATUS_L6RSP;
3209 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3210 err_st = HCHK_STATUS_L4CON;
3211 else {
3212 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3213 args[cur_arg], args[cur_arg+1]);
3214 goto error;
3215 }
3216 cur_arg++;
3217 }
3218 else if (strcmp(args[cur_arg], "status-code") == 0) {
3219 int idx = 0;
3220
3221 if (in_pattern) {
3222 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3223 goto error;
3224 }
3225 if (!*(args[cur_arg+1])) {
3226 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3227 goto error;
3228 }
3229
3230 cur_arg++;
3231 release_sample_expr(status_expr);
3232 px->conf.args.ctx = ARGC_SRV;
3233 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003234 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003235 if (!status_expr) {
3236 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3237 goto error;
3238 }
3239 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3240 memprintf(errmsg, "error detected while parsing status-code expression : "
3241 " fetch method '%s' extracts information from '%s', "
3242 "none of which is available here.\n",
3243 args[cur_arg], sample_src_names(status_expr->fetch->use));
3244 goto error;
3245 }
3246 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3247 }
3248 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3249 if (in_pattern) {
3250 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3251 goto error;
3252 }
3253 if (!*(args[cur_arg+1])) {
3254 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3255 goto error;
3256 }
3257 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3258 tout_st = HCHK_STATUS_L7TOUT;
3259 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3260 tout_st = HCHK_STATUS_L6TOUT;
3261 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3262 tout_st = HCHK_STATUS_L4TOUT;
3263 else {
3264 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3265 args[cur_arg], args[cur_arg+1]);
3266 goto error;
3267 }
3268 cur_arg++;
3269 }
3270 else {
3271 if (proto == TCPCHK_RULES_HTTP_CHK) {
3272 bad_http_kw:
3273 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3274 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3275 }
3276 else {
3277 bad_tcp_kw:
3278 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3279 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3280 }
3281 goto error;
3282 }
3283 next:
3284 cur_arg++;
3285 }
3286
3287 chk = calloc(1, sizeof(*chk));
3288 if (!chk) {
3289 memprintf(errmsg, "out of memory");
3290 goto error;
3291 }
3292 chk->action = TCPCHK_ACT_EXPECT;
3293 LIST_INIT(&chk->expect.onerror_fmt);
3294 LIST_INIT(&chk->expect.onsuccess_fmt);
3295 chk->comment = comment; comment = NULL;
3296 chk->expect.type = type;
3297 chk->expect.min_recv = min_recv;
3298 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3299 chk->expect.ok_status = ok_st;
3300 chk->expect.err_status = err_st;
3301 chk->expect.tout_status = tout_st;
3302 chk->expect.status_expr = status_expr; status_expr = NULL;
3303
3304 if (on_success_msg) {
3305 px->conf.args.ctx = ARGC_SRV;
3306 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3307 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3308 goto error;
3309 }
3310 }
3311 if (on_error_msg) {
3312 px->conf.args.ctx = ARGC_SRV;
3313 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3314 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3315 goto error;
3316 }
3317 }
3318
3319 switch (chk->expect.type) {
3320 case TCPCHK_EXPECT_HTTP_STATUS: {
3321 const char *p = pattern;
3322 unsigned int c1,c2;
3323
3324 chk->expect.codes.codes = NULL;
3325 chk->expect.codes.num = 0;
3326 while (1) {
3327 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3328 if (*p == '-') {
3329 p++;
3330 c2 = read_uint(&p, pattern + strlen(pattern));
3331 }
3332 if (c1 > c2) {
3333 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3334 goto error;
3335 }
3336
3337 chk->expect.codes.num++;
3338 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3339 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3340 if (!chk->expect.codes.codes) {
3341 memprintf(errmsg, "out of memory");
3342 goto error;
3343 }
3344 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3345 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3346
3347 if (*p == '\0')
3348 break;
3349 if (*p != ',') {
3350 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3351 goto error;
3352 }
3353 p++;
3354 }
3355 break;
3356 }
3357 case TCPCHK_EXPECT_STRING:
3358 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003359 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003360 if (!isttest(chk->expect.data)) {
3361 memprintf(errmsg, "out of memory");
3362 goto error;
3363 }
3364 break;
3365 case TCPCHK_EXPECT_BINARY: {
3366 int len = chk->expect.data.len;
3367
3368 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3369 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3370 goto error;
3371 }
3372 chk->expect.data.len = len;
3373 break;
3374 }
3375 case TCPCHK_EXPECT_STRING_REGEX:
3376 case TCPCHK_EXPECT_BINARY_REGEX:
3377 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3378 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3379 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3380 if (!chk->expect.regex)
3381 goto error;
3382 break;
3383
3384 case TCPCHK_EXPECT_STRING_LF:
3385 case TCPCHK_EXPECT_BINARY_LF:
3386 case TCPCHK_EXPECT_HTTP_BODY_LF:
3387 LIST_INIT(&chk->expect.fmt);
3388 px->conf.args.ctx = ARGC_SRV;
3389 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3390 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3391 goto error;
3392 }
3393 break;
3394
3395 case TCPCHK_EXPECT_HTTP_HEADER:
3396 if (!npat) {
3397 memprintf(errmsg, "unexpected error, undefined header name pattern");
3398 goto error;
3399 }
3400 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3401 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3402 if (!chk->expect.hdr.name_re)
3403 goto error;
3404 }
3405 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3406 px->conf.args.ctx = ARGC_SRV;
3407 LIST_INIT(&chk->expect.hdr.name_fmt);
3408 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3409 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3410 goto error;
3411 }
3412 }
3413 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003414 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003415 if (!isttest(chk->expect.hdr.name)) {
3416 memprintf(errmsg, "out of memory");
3417 goto error;
3418 }
3419 }
3420
3421 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3422 chk->expect.hdr.value = IST_NULL;
3423 break;
3424 }
3425
3426 if (!vpat) {
3427 memprintf(errmsg, "unexpected error, undefined header value pattern");
3428 goto error;
3429 }
3430 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3431 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3432 if (!chk->expect.hdr.value_re)
3433 goto error;
3434 }
3435 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3436 px->conf.args.ctx = ARGC_SRV;
3437 LIST_INIT(&chk->expect.hdr.value_fmt);
3438 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3439 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3440 goto error;
3441 }
3442 }
3443 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003444 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003445 if (!isttest(chk->expect.hdr.value)) {
3446 memprintf(errmsg, "out of memory");
3447 goto error;
3448 }
3449 }
3450
3451 break;
3452 case TCPCHK_EXPECT_CUSTOM:
3453 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3454 break;
3455 case TCPCHK_EXPECT_UNDEF:
3456 memprintf(errmsg, "pattern not found");
3457 goto error;
3458 }
3459
3460 /* All tcp-check expect points back to the first inverse expect rule in
3461 * a chain of one or more expect rule, potentially itself.
3462 */
3463 chk->expect.head = chk;
3464 list_for_each_entry_rev(prev_check, rules, list) {
3465 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3466 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3467 chk->expect.head = prev_check;
3468 continue;
3469 }
3470 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3471 break;
3472 }
3473 return chk;
3474
3475 error:
3476 free_tcpcheck(chk, 0);
3477 free(comment);
3478 release_sample_expr(status_expr);
3479 return NULL;
3480}
3481
3482/* Overwrites fields of the old http send rule with those of the new one. When
3483 * replaced, old values are freed and replaced by the new ones. New values are
3484 * not copied but transferred. At the end <new> should be empty and can be
3485 * safely released. This function never fails.
3486 */
3487void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3488{
3489 struct logformat_node *lf, *lfb;
3490 struct tcpcheck_http_hdr *hdr, *bhdr;
3491
3492
3493 if (new->send.http.meth.str.area) {
3494 free(old->send.http.meth.str.area);
3495 old->send.http.meth.meth = new->send.http.meth.meth;
3496 old->send.http.meth.str.area = new->send.http.meth.str.area;
3497 old->send.http.meth.str.data = new->send.http.meth.str.data;
3498 new->send.http.meth.str = BUF_NULL;
3499 }
3500
3501 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3502 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3503 istfree(&old->send.http.uri);
3504 else
3505 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3506 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3507 old->send.http.uri = new->send.http.uri;
3508 new->send.http.uri = IST_NULL;
3509 }
3510 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3511 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3512 istfree(&old->send.http.uri);
3513 else
3514 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3515 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3516 LIST_INIT(&old->send.http.uri_fmt);
3517 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003518 LIST_DELETE(&lf->list);
3519 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003520 }
3521 }
3522
3523 if (isttest(new->send.http.vsn)) {
3524 istfree(&old->send.http.vsn);
3525 old->send.http.vsn = new->send.http.vsn;
3526 new->send.http.vsn = IST_NULL;
3527 }
3528
3529 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3530 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003531 LIST_DELETE(&hdr->list);
3532 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003533 }
3534
3535 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3536 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3537 istfree(&old->send.http.body);
3538 else
3539 free_tcpcheck_fmt(&old->send.http.body_fmt);
3540 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3541 old->send.http.body = new->send.http.body;
3542 new->send.http.body = IST_NULL;
3543 }
3544 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3545 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3546 istfree(&old->send.http.body);
3547 else
3548 free_tcpcheck_fmt(&old->send.http.body_fmt);
3549 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3550 LIST_INIT(&old->send.http.body_fmt);
3551 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003552 LIST_DELETE(&lf->list);
3553 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003554 }
3555 }
3556}
3557
3558/* Internal function used to add an http-check rule in a list during the config
3559 * parsing step. Depending on its type, and the previously inserted rules, a
3560 * specific action may be performed or an error may be reported. This functions
3561 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3562 * message.
3563 */
3564int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3565{
3566 struct tcpcheck_rule *r;
3567
3568 /* the implicit send rule coming from an "option httpchk" line must be
3569 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003570 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003571 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003572 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003573 * sure the ruleset remains valid.
3574 */
3575
3576 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3577 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3578 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3579 * following tests are performed :
3580 *
3581 * 1- If there is no such rule or if it is not a send rule, the implicit send
3582 * rule is pushed in front of the ruleset
3583 *
3584 * 2- If it is another implicit send rule, it is replaced with the new one.
3585 *
3586 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3587 * both, overwriting the old send rule (the explicit one) with info of the
3588 * new send rule (the implicit one).
3589 */
3590 r = get_first_tcpcheck_rule(rules);
3591 if (r && r->action == TCPCHK_ACT_CONNECT)
3592 r = get_next_tcpcheck_rule(rules, r);
3593 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003594 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003595 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003596 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003597 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003598 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003599 }
3600 else {
3601 tcpcheck_overwrite_send_http_rule(r, chk);
3602 free_tcpcheck(chk, 0);
3603 }
3604 }
3605 else {
3606 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3607 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3608 * with an existing implicit send rule, if any. At the end, if there is no error,
3609 * the rule is appended to the list.
3610 */
3611
3612 r = get_last_tcpcheck_rule(rules);
3613 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3614 /* no error */;
3615 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3616 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3617 chk->index+1);
3618 return 0;
3619 }
3620 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3621 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3622 chk->index+1);
3623 return 0;
3624 }
3625 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3626 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3627 chk->index+1);
3628 return 0;
3629 }
3630
3631 if (chk->action == TCPCHK_ACT_SEND) {
3632 r = get_first_tcpcheck_rule(rules);
3633 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3634 tcpcheck_overwrite_send_http_rule(r, chk);
3635 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003636 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003637 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3638 chk = r;
3639 }
3640 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003641 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003642 }
3643 return 1;
3644}
3645
3646/* Check tcp-check health-check configuration for the proxy <px>. */
3647static int check_proxy_tcpcheck(struct proxy *px)
3648{
3649 struct tcpcheck_rule *chk, *back;
3650 char *comment = NULL, *errmsg = NULL;
3651 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003652 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003653
3654 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3655 deinit_proxy_tcpcheck(px);
3656 goto out;
3657 }
3658
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003659 ha_free(&px->check_command);
3660 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003661
3662 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003663 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003664 ret |= ERR_ALERT | ERR_FATAL;
3665 goto out;
3666 }
3667
3668 /* HTTP ruleset only : */
3669 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3670 struct tcpcheck_rule *next;
3671
3672 /* move remaining implicit send rule from "option httpchk" line to the right place.
3673 * If such rule exists, it must be the first one. In this case, the rule is moved
3674 * after the first connect rule, if any. Otherwise, nothing is done.
3675 */
3676 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3677 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3678 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3679 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003680 LIST_DELETE(&chk->list);
3681 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003682 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003683 }
3684 }
3685
3686 /* add implicit expect rule if the last one is a send. It is inherited from previous
3687 * versions where the http expect rule was optional. Now it is possible to chained
3688 * send/expect rules but the last expect may still be implicit.
3689 */
3690 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3691 if (chk && chk->action == TCPCHK_ACT_SEND) {
3692 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3693 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3694 px->conf.file, px->conf.line, &errmsg);
3695 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003696 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003697 "(%s).\n", px->id, errmsg);
3698 free(errmsg);
3699 ret |= ERR_ALERT | ERR_FATAL;
3700 goto out;
3701 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003702 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003703 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003704 }
3705 }
3706
3707 /* For all ruleset: */
3708
3709 /* If there is no connect rule preceding all send / expect rules, an
3710 * implicit one is inserted before all others.
3711 */
3712 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3713 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3714 chk = calloc(1, sizeof(*chk));
3715 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003716 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003717 "(out of memory).\n", px->id);
3718 ret |= ERR_ALERT | ERR_FATAL;
3719 goto out;
3720 }
3721 chk->action = TCPCHK_ACT_CONNECT;
3722 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003723 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003724 }
3725
3726 /* Remove all comment rules. To do so, when a such rule is found, the
3727 * comment is assigned to the following rule(s).
3728 */
3729 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003730 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3731 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003732
3733 prev_action = chk->action;
3734 switch (chk->action) {
3735 case TCPCHK_ACT_COMMENT:
3736 free(comment);
3737 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003738 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003739 free(chk);
3740 break;
3741 case TCPCHK_ACT_CONNECT:
3742 if (!chk->comment && comment)
3743 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003744 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003745 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003746 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003747 break;
3748 case TCPCHK_ACT_SEND:
3749 case TCPCHK_ACT_EXPECT:
3750 if (!chk->comment && comment)
3751 chk->comment = strdup(comment);
3752 break;
3753 }
3754 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003755 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003756
3757 out:
3758 return ret;
3759}
3760
3761void deinit_proxy_tcpcheck(struct proxy *px)
3762{
3763 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3764 px->tcpcheck_rules.flags = 0;
3765 px->tcpcheck_rules.list = NULL;
3766}
3767
3768static void deinit_tcpchecks()
3769{
3770 struct tcpcheck_ruleset *rs;
3771 struct tcpcheck_rule *r, *rb;
3772 struct ebpt_node *node, *next;
3773
3774 node = ebpt_first(&shared_tcpchecks);
3775 while (node) {
3776 next = ebpt_next(node);
3777 ebpt_delete(node);
3778 free(node->key);
3779 rs = container_of(node, typeof(*rs), node);
3780 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003781 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003782 free_tcpcheck(r, 0);
3783 }
3784 free(rs);
3785 node = next;
3786 }
3787}
3788
3789int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3790{
3791 struct tcpcheck_rule *tcpcheck, *prev_check;
3792 struct tcpcheck_expect *expect;
3793
Willy Tarreau6922e552021-03-22 21:11:45 +01003794 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003795 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003796 tcpcheck->action = TCPCHK_ACT_EXPECT;
3797
3798 expect = &tcpcheck->expect;
3799 expect->type = TCPCHK_EXPECT_STRING;
3800 LIST_INIT(&expect->onerror_fmt);
3801 LIST_INIT(&expect->onsuccess_fmt);
3802 expect->ok_status = HCHK_STATUS_L7OKD;
3803 expect->err_status = HCHK_STATUS_L7RSP;
3804 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003805 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003806 if (!isttest(expect->data)) {
3807 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3808 return 0;
3809 }
3810
3811 /* All tcp-check expect points back to the first inverse expect rule
3812 * in a chain of one or more expect rule, potentially itself.
3813 */
3814 tcpcheck->expect.head = tcpcheck;
3815 list_for_each_entry_rev(prev_check, rules->list, list) {
3816 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3817 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3818 tcpcheck->expect.head = prev_check;
3819 continue;
3820 }
3821 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3822 break;
3823 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003824 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003825 return 1;
3826}
3827
3828int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3829{
3830 struct tcpcheck_rule *tcpcheck;
3831 struct tcpcheck_send *send;
3832 const char *in;
3833 char *dst;
3834 int i;
3835
Willy Tarreau6922e552021-03-22 21:11:45 +01003836 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003837 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003838 tcpcheck->action = TCPCHK_ACT_SEND;
3839
3840 send = &tcpcheck->send;
3841 send->type = TCPCHK_SEND_STRING;
3842
3843 for (i = 0; strs[i]; i++)
3844 send->data.len += strlen(strs[i]);
3845
3846 send->data.ptr = malloc(istlen(send->data) + 1);
3847 if (!isttest(send->data)) {
3848 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3849 return 0;
3850 }
3851
3852 dst = istptr(send->data);
3853 for (i = 0; strs[i]; i++)
3854 for (in = strs[i]; (*dst = *in++); dst++);
3855 *dst = 0;
3856
Willy Tarreau2b718102021-04-21 07:32:39 +02003857 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003858 return 1;
3859}
3860
3861/* Parses the "tcp-check" proxy keyword */
3862static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003863 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003864 char **errmsg)
3865{
3866 struct tcpcheck_ruleset *rs = NULL;
3867 struct tcpcheck_rule *chk = NULL;
3868 int index, cur_arg, ret = 0;
3869
3870 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3871 ret = 1;
3872
3873 /* Deduce the ruleset name from the proxy info */
3874 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3875 ((curpx == defpx) ? "defaults" : curpx->id),
3876 curpx->conf.file, curpx->conf.line);
3877
3878 rs = find_tcpcheck_ruleset(b_orig(&trash));
3879 if (rs == NULL) {
3880 rs = create_tcpcheck_ruleset(b_orig(&trash));
3881 if (rs == NULL) {
3882 memprintf(errmsg, "out of memory.\n");
3883 goto error;
3884 }
3885 }
3886
3887 index = 0;
3888 if (!LIST_ISEMPTY(&rs->rules)) {
3889 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3890 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003891 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003892 }
3893
3894 cur_arg = 1;
3895 if (strcmp(args[cur_arg], "connect") == 0)
3896 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3897 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3898 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3899 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3900 else if (strcmp(args[cur_arg], "expect") == 0)
3901 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3902 else if (strcmp(args[cur_arg], "comment") == 0)
3903 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3904 else {
3905 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3906
3907 if (!kw) {
3908 action_kw_tcp_check_build_list(&trash);
3909 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3910 "%s%s. but got '%s'",
3911 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3912 goto error;
3913 }
3914 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3915 }
3916
3917 if (!chk) {
3918 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3919 goto error;
3920 }
3921 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3922
3923 /* No error: add the tcp-check rule in the list */
3924 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003925 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003926
3927 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3928 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3929 /* Use this ruleset if the proxy already has tcp-check enabled */
3930 curpx->tcpcheck_rules.list = &rs->rules;
3931 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3932 }
3933 else {
3934 /* mark this ruleset as unused for now */
3935 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3936 }
3937
3938 return ret;
3939
3940 error:
3941 free_tcpcheck(chk, 0);
3942 free_tcpcheck_ruleset(rs);
3943 return -1;
3944}
3945
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003946/* Parses the "http-check" proxy keyword */
3947static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003948 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003949 char **errmsg)
3950{
3951 struct tcpcheck_ruleset *rs = NULL;
3952 struct tcpcheck_rule *chk = NULL;
3953 int index, cur_arg, ret = 0;
3954
3955 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3956 ret = 1;
3957
3958 cur_arg = 1;
3959 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3960 /* enable a graceful server shutdown on an HTTP 404 response */
3961 curpx->options |= PR_O_DISABLE404;
3962 if (too_many_args(1, args, errmsg, NULL))
3963 goto error;
3964 goto out;
3965 }
3966 else if (strcmp(args[cur_arg], "send-state") == 0) {
3967 /* enable emission of the apparent state of a server in HTTP checks */
3968 curpx->options2 |= PR_O2_CHK_SNDST;
3969 if (too_many_args(1, args, errmsg, NULL))
3970 goto error;
3971 goto out;
3972 }
3973
3974 /* Deduce the ruleset name from the proxy info */
3975 chunk_printf(&trash, "*http-check-%s_%s-%d",
3976 ((curpx == defpx) ? "defaults" : curpx->id),
3977 curpx->conf.file, curpx->conf.line);
3978
3979 rs = find_tcpcheck_ruleset(b_orig(&trash));
3980 if (rs == NULL) {
3981 rs = create_tcpcheck_ruleset(b_orig(&trash));
3982 if (rs == NULL) {
3983 memprintf(errmsg, "out of memory.\n");
3984 goto error;
3985 }
3986 }
3987
3988 index = 0;
3989 if (!LIST_ISEMPTY(&rs->rules)) {
3990 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3991 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
3992 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003993 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003994 }
3995
3996 if (strcmp(args[cur_arg], "connect") == 0)
3997 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3998 else if (strcmp(args[cur_arg], "send") == 0)
3999 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4000 else if (strcmp(args[cur_arg], "expect") == 0)
4001 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4002 file, line, errmsg);
4003 else if (strcmp(args[cur_arg], "comment") == 0)
4004 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4005 else {
4006 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4007
4008 if (!kw) {
4009 action_kw_tcp_check_build_list(&trash);
4010 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4011 " 'send', 'expect'%s%s. but got '%s'",
4012 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4013 goto error;
4014 }
4015 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4016 }
4017
4018 if (!chk) {
4019 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4020 goto error;
4021 }
4022 ret = (*errmsg != NULL); /* Handle warning */
4023
4024 chk->index = index;
4025 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4026 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4027 /* Use this ruleset if the proxy already has http-check enabled */
4028 curpx->tcpcheck_rules.list = &rs->rules;
4029 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4030 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4031 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4032 curpx->tcpcheck_rules.list = NULL;
4033 goto error;
4034 }
4035 }
4036 else {
4037 /* mark this ruleset as unused for now */
4038 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004039 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004040 }
4041
4042 out:
4043 return ret;
4044
4045 error:
4046 free_tcpcheck(chk, 0);
4047 free_tcpcheck_ruleset(rs);
4048 return -1;
4049}
4050
4051/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004052int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004053 const char *file, int line)
4054{
4055 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4056 static char *redis_res = "+PONG\r\n";
4057
4058 struct tcpcheck_ruleset *rs = NULL;
4059 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4060 struct tcpcheck_rule *chk;
4061 char *errmsg = NULL;
4062 int err_code = 0;
4063
4064 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4065 err_code |= ERR_WARN;
4066
4067 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4068 goto out;
4069
4070 curpx->options2 &= ~PR_O2_CHK_ANY;
4071 curpx->options2 |= PR_O2_TCPCHK_CHK;
4072
4073 free_tcpcheck_vars(&rules->preset_vars);
4074 rules->list = NULL;
4075 rules->flags = 0;
4076
4077 rs = find_tcpcheck_ruleset("*redis-check");
4078 if (rs)
4079 goto ruleset_found;
4080
4081 rs = create_tcpcheck_ruleset("*redis-check");
4082 if (rs == NULL) {
4083 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4084 goto error;
4085 }
4086
4087 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4088 1, curpx, &rs->rules, file, line, &errmsg);
4089 if (!chk) {
4090 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4091 goto error;
4092 }
4093 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004094 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004095
4096 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4097 "error-status", "L7STS",
4098 "on-error", "%[res.payload(0,0),cut_crlf]",
4099 "on-success", "Redis server is ok",
4100 ""},
4101 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4102 if (!chk) {
4103 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4104 goto error;
4105 }
4106 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004107 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004108
4109 ruleset_found:
4110 rules->list = &rs->rules;
4111 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4112 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4113
4114 out:
4115 free(errmsg);
4116 return err_code;
4117
4118 error:
4119 free_tcpcheck_ruleset(rs);
4120 err_code |= ERR_ALERT | ERR_FATAL;
4121 goto out;
4122}
4123
4124
4125/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004126int 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 +01004127 const char *file, int line)
4128{
4129 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4130 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4131 *
4132 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4133 */
4134 static char sslv3_client_hello[] = {
4135 "16" /* ContentType : 0x16 = Handshake */
4136 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4137 "0079" /* ContentLength : 0x79 bytes after this one */
4138 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4139 "000075" /* HandshakeLength : 0x75 bytes after this one */
4140 "0300" /* Hello Version : 0x0300 = v3 */
4141 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4142 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4143 "00" /* Session ID length : empty (no session ID) */
4144 "004E" /* Cipher Suite Length : 78 bytes after this one */
4145 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4146 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4147 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4148 "000D" "000E" "000F" "0010" /* various bit lengths, */
4149 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4150 "0015" "0016" "0017" "0018"
4151 "0019" "001A" "001B" "002F"
4152 "0030" "0031" "0032" "0033"
4153 "0034" "0035" "0036" "0037"
4154 "0038" "0039" "003A"
4155 "01" /* Compression Length : 0x01 = 1 byte for types */
4156 "00" /* Compression Type : 0x00 = NULL compression */
4157 };
4158
4159 struct tcpcheck_ruleset *rs = NULL;
4160 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4161 struct tcpcheck_rule *chk;
4162 char *errmsg = NULL;
4163 int err_code = 0;
4164
4165 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4166 err_code |= ERR_WARN;
4167
4168 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4169 goto out;
4170
4171 curpx->options2 &= ~PR_O2_CHK_ANY;
4172 curpx->options2 |= PR_O2_TCPCHK_CHK;
4173
4174 free_tcpcheck_vars(&rules->preset_vars);
4175 rules->list = NULL;
4176 rules->flags = 0;
4177
4178 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4179 if (rs)
4180 goto ruleset_found;
4181
4182 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4183 if (rs == NULL) {
4184 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4185 goto error;
4186 }
4187
4188 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4189 1, curpx, &rs->rules, file, line, &errmsg);
4190 if (!chk) {
4191 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4192 goto error;
4193 }
4194 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004195 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004196
4197 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4198 "min-recv", "5", "ok-status", "L6OK",
4199 "error-status", "L6RSP", "tout-status", "L6TOUT",
4200 ""},
4201 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4202 if (!chk) {
4203 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4204 goto error;
4205 }
4206 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004207 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004208
4209 ruleset_found:
4210 rules->list = &rs->rules;
4211 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4212 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4213
4214 out:
4215 free(errmsg);
4216 return err_code;
4217
4218 error:
4219 free_tcpcheck_ruleset(rs);
4220 err_code |= ERR_ALERT | ERR_FATAL;
4221 goto out;
4222}
4223
4224/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004225int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004226 const char *file, int line)
4227{
4228 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4229
4230 struct tcpcheck_ruleset *rs = NULL;
4231 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4232 struct tcpcheck_rule *chk;
4233 struct tcpcheck_var *var = NULL;
4234 char *cmd = NULL, *errmsg = NULL;
4235 int err_code = 0;
4236
4237 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4238 err_code |= ERR_WARN;
4239
4240 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4241 goto out;
4242
4243 curpx->options2 &= ~PR_O2_CHK_ANY;
4244 curpx->options2 |= PR_O2_TCPCHK_CHK;
4245
4246 free_tcpcheck_vars(&rules->preset_vars);
4247 rules->list = NULL;
4248 rules->flags = 0;
4249
4250 cur_arg += 2;
4251 if (*args[cur_arg] && *args[cur_arg+1] &&
4252 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4253 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4254 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4255 if (cmd)
4256 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4257 }
4258 else {
4259 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4260 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4261 cmd = strdup("HELO localhost");
4262 }
4263
4264 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4265 if (cmd == NULL || var == NULL) {
4266 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4267 goto error;
4268 }
4269 var->data.type = SMP_T_STR;
4270 var->data.u.str.area = cmd;
4271 var->data.u.str.data = strlen(cmd);
4272 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004273 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004274 cmd = NULL;
4275 var = NULL;
4276
4277 rs = find_tcpcheck_ruleset("*smtp-check");
4278 if (rs)
4279 goto ruleset_found;
4280
4281 rs = create_tcpcheck_ruleset("*smtp-check");
4282 if (rs == NULL) {
4283 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4284 goto error;
4285 }
4286
4287 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4288 1, curpx, &rs->rules, file, line, &errmsg);
4289 if (!chk) {
4290 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4291 goto error;
4292 }
4293 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004294 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004295
4296 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4297 "min-recv", "4",
4298 "error-status", "L7RSP",
4299 "on-error", "%[res.payload(0,0),cut_crlf]",
4300 ""},
4301 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4302 if (!chk) {
4303 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4304 goto error;
4305 }
4306 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004307 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004308
4309 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4310 "min-recv", "4",
4311 "error-status", "L7STS",
4312 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4313 "status-code", "res.payload(0,3)",
4314 ""},
4315 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4316 if (!chk) {
4317 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4318 goto error;
4319 }
4320 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004321 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004322
4323 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4324 1, curpx, &rs->rules, file, line, &errmsg);
4325 if (!chk) {
4326 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4327 goto error;
4328 }
4329 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004330 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004331
4332 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4333 "min-recv", "4",
4334 "error-status", "L7STS",
4335 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4336 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4337 "status-code", "res.payload(0,3)",
4338 ""},
4339 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4340 if (!chk) {
4341 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4342 goto error;
4343 }
4344 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004345 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004346
4347 ruleset_found:
4348 rules->list = &rs->rules;
4349 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4350 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4351
4352 out:
4353 free(errmsg);
4354 return err_code;
4355
4356 error:
4357 free(cmd);
4358 free(var);
4359 free_tcpcheck_vars(&rules->preset_vars);
4360 free_tcpcheck_ruleset(rs);
4361 err_code |= ERR_ALERT | ERR_FATAL;
4362 goto out;
4363}
4364
4365/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004366int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004367 const char *file, int line)
4368{
4369 static char pgsql_req[] = {
4370 "%[var(check.plen),htonl,hex]" /* The packet length*/
4371 "00030000" /* the version 3.0 */
4372 "7573657200" /* "user" key */
4373 "%[var(check.username),hex]00" /* the username */
4374 "00"
4375 };
4376
4377 struct tcpcheck_ruleset *rs = NULL;
4378 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4379 struct tcpcheck_rule *chk;
4380 struct tcpcheck_var *var = NULL;
4381 char *user = NULL, *errmsg = NULL;
4382 size_t packetlen = 0;
4383 int err_code = 0;
4384
4385 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4386 err_code |= ERR_WARN;
4387
4388 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4389 goto out;
4390
4391 curpx->options2 &= ~PR_O2_CHK_ANY;
4392 curpx->options2 |= PR_O2_TCPCHK_CHK;
4393
4394 free_tcpcheck_vars(&rules->preset_vars);
4395 rules->list = NULL;
4396 rules->flags = 0;
4397
4398 cur_arg += 2;
4399 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4400 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4401 file, line, args[0], args[1]);
4402 goto error;
4403 }
4404 if (strcmp(args[cur_arg], "user") == 0) {
4405 packetlen = 15 + strlen(args[cur_arg+1]);
4406 user = strdup(args[cur_arg+1]);
4407
4408 var = create_tcpcheck_var(ist("check.username"));
4409 if (user == NULL || var == NULL) {
4410 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4411 goto error;
4412 }
4413 var->data.type = SMP_T_STR;
4414 var->data.u.str.area = user;
4415 var->data.u.str.data = strlen(user);
4416 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004417 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004418 user = NULL;
4419 var = NULL;
4420
4421 var = create_tcpcheck_var(ist("check.plen"));
4422 if (var == NULL) {
4423 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4424 goto error;
4425 }
4426 var->data.type = SMP_T_SINT;
4427 var->data.u.sint = packetlen;
4428 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004429 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004430 var = NULL;
4431 }
4432 else {
4433 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4434 file, line, args[0], args[1]);
4435 goto error;
4436 }
4437
4438 rs = find_tcpcheck_ruleset("*pgsql-check");
4439 if (rs)
4440 goto ruleset_found;
4441
4442 rs = create_tcpcheck_ruleset("*pgsql-check");
4443 if (rs == NULL) {
4444 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4445 goto error;
4446 }
4447
4448 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4449 1, curpx, &rs->rules, file, line, &errmsg);
4450 if (!chk) {
4451 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4452 goto error;
4453 }
4454 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004455 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004456
4457 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4458 1, curpx, &rs->rules, file, line, &errmsg);
4459 if (!chk) {
4460 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4461 goto error;
4462 }
4463 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004464 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004465
4466 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4467 "min-recv", "5",
4468 "error-status", "L7RSP",
4469 "on-error", "%[res.payload(6,0)]",
4470 ""},
4471 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4472 if (!chk) {
4473 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4474 goto error;
4475 }
4476 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004477 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004478
4479 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4480 "min-recv", "9",
4481 "error-status", "L7STS",
4482 "on-success", "PostgreSQL server is ok",
4483 "on-error", "PostgreSQL unknown error",
4484 ""},
4485 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4486 if (!chk) {
4487 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4488 goto error;
4489 }
4490 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004491 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004492
4493 ruleset_found:
4494 rules->list = &rs->rules;
4495 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4496 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4497
4498 out:
4499 free(errmsg);
4500 return err_code;
4501
4502 error:
4503 free(user);
4504 free(var);
4505 free_tcpcheck_vars(&rules->preset_vars);
4506 free_tcpcheck_ruleset(rs);
4507 err_code |= ERR_ALERT | ERR_FATAL;
4508 goto out;
4509}
4510
4511
4512/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004513int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004514 const char *file, int line)
4515{
4516 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4517 * const char mysql40_client_auth_pkt[] = {
4518 * "\x0e\x00\x00" // packet length
4519 * "\x01" // packet number
4520 * "\x00\x00" // client capabilities
4521 * "\x00\x00\x01" // max packet
4522 * "haproxy\x00" // username (null terminated string)
4523 * "\x00" // filler (always 0x00)
4524 * "\x01\x00\x00" // packet length
4525 * "\x00" // packet number
4526 * "\x01" // COM_QUIT command
4527 * };
4528 */
4529 static char mysql40_rsname[] = "*mysql40-check";
4530 static char mysql40_req[] = {
4531 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4532 "0080" /* client capabilities */
4533 "000001" /* max packet */
4534 "%[var(check.username),hex]00" /* the username */
4535 "00" /* filler (always 0x00) */
4536 "010000" /* packet length*/
4537 "00" /* sequence ID */
4538 "01" /* COM_QUIT command */
4539 };
4540
4541 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4542 * const char mysql41_client_auth_pkt[] = {
4543 * "\x0e\x00\x00\" // packet length
4544 * "\x01" // packet number
4545 * "\x00\x00\x00\x00" // client capabilities
4546 * "\x00\x00\x00\x01" // max packet
4547 * "\x21" // character set (UTF-8)
4548 * char[23] // All zeroes
4549 * "haproxy\x00" // username (null terminated string)
4550 * "\x00" // filler (always 0x00)
4551 * "\x01\x00\x00" // packet length
4552 * "\x00" // packet number
4553 * "\x01" // COM_QUIT command
4554 * };
4555 */
4556 static char mysql41_rsname[] = "*mysql41-check";
4557 static char mysql41_req[] = {
4558 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4559 "00820000" /* client capabilities */
4560 "00800001" /* max packet */
4561 "21" /* character set (UTF-8) */
4562 "000000000000000000000000" /* 23 bytes, al zeroes */
4563 "0000000000000000000000"
4564 "%[var(check.username),hex]00" /* the username */
4565 "00" /* filler (always 0x00) */
4566 "010000" /* packet length*/
4567 "00" /* sequence ID */
4568 "01" /* COM_QUIT command */
4569 };
4570
4571 struct tcpcheck_ruleset *rs = NULL;
4572 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4573 struct tcpcheck_rule *chk;
4574 struct tcpcheck_var *var = NULL;
4575 char *mysql_rsname = "*mysql-check";
4576 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4577 int index = 0, err_code = 0;
4578
4579 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4580 err_code |= ERR_WARN;
4581
4582 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4583 goto out;
4584
4585 curpx->options2 &= ~PR_O2_CHK_ANY;
4586 curpx->options2 |= PR_O2_TCPCHK_CHK;
4587
4588 free_tcpcheck_vars(&rules->preset_vars);
4589 rules->list = NULL;
4590 rules->flags = 0;
4591
4592 cur_arg += 2;
4593 if (*args[cur_arg]) {
4594 int packetlen, userlen;
4595
4596 if (strcmp(args[cur_arg], "user") != 0) {
4597 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4598 file, line, args[0], args[1], args[cur_arg]);
4599 goto error;
4600 }
4601
4602 if (*(args[cur_arg+1]) == 0) {
4603 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4604 file, line, args[0], args[1], args[cur_arg]);
4605 goto error;
4606 }
4607
4608 hdr = calloc(4, sizeof(*hdr));
4609 user = strdup(args[cur_arg+1]);
4610 userlen = strlen(args[cur_arg+1]);
4611
4612 if (hdr == NULL || user == NULL) {
4613 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4614 goto error;
4615 }
4616
4617 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4618 packetlen = userlen + 7 + 27;
4619 mysql_req = mysql41_req;
4620 mysql_rsname = mysql41_rsname;
4621 }
4622 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4623 packetlen = userlen + 7;
4624 mysql_req = mysql40_req;
4625 mysql_rsname = mysql40_rsname;
4626 }
4627 else {
4628 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4629 file, line, args[cur_arg], args[cur_arg+2]);
4630 goto error;
4631 }
4632
4633 hdr[0] = (unsigned char)(packetlen & 0xff);
4634 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4635 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4636 hdr[3] = 1;
4637
4638 var = create_tcpcheck_var(ist("check.header"));
4639 if (var == NULL) {
4640 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4641 goto error;
4642 }
4643 var->data.type = SMP_T_STR;
4644 var->data.u.str.area = hdr;
4645 var->data.u.str.data = 4;
4646 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004647 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004648 hdr = NULL;
4649 var = NULL;
4650
4651 var = create_tcpcheck_var(ist("check.username"));
4652 if (var == NULL) {
4653 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4654 goto error;
4655 }
4656 var->data.type = SMP_T_STR;
4657 var->data.u.str.area = user;
4658 var->data.u.str.data = strlen(user);
4659 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004660 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004661 user = NULL;
4662 var = NULL;
4663 }
4664
4665 rs = find_tcpcheck_ruleset(mysql_rsname);
4666 if (rs)
4667 goto ruleset_found;
4668
4669 rs = create_tcpcheck_ruleset(mysql_rsname);
4670 if (rs == NULL) {
4671 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4672 goto error;
4673 }
4674
4675 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4676 1, curpx, &rs->rules, file, line, &errmsg);
4677 if (!chk) {
4678 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4679 goto error;
4680 }
4681 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004682 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004683
4684 if (mysql_req) {
4685 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4686 1, curpx, &rs->rules, file, line, &errmsg);
4687 if (!chk) {
4688 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4689 goto error;
4690 }
4691 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004692 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004693 }
4694
4695 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4696 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4697 if (!chk) {
4698 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4699 goto error;
4700 }
4701 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4702 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004703 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004704
4705 if (mysql_req) {
4706 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4707 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4708 if (!chk) {
4709 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4710 goto error;
4711 }
4712 chk->expect.custom = tcpcheck_mysql_expect_ok;
4713 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004714 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004715 }
4716
4717 ruleset_found:
4718 rules->list = &rs->rules;
4719 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4720 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4721
4722 out:
4723 free(errmsg);
4724 return err_code;
4725
4726 error:
4727 free(hdr);
4728 free(user);
4729 free(var);
4730 free_tcpcheck_vars(&rules->preset_vars);
4731 free_tcpcheck_ruleset(rs);
4732 err_code |= ERR_ALERT | ERR_FATAL;
4733 goto out;
4734}
4735
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004736int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004737 const char *file, int line)
4738{
4739 static char *ldap_req = "300C020101600702010304008000";
4740
4741 struct tcpcheck_ruleset *rs = NULL;
4742 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4743 struct tcpcheck_rule *chk;
4744 char *errmsg = NULL;
4745 int err_code = 0;
4746
4747 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4748 err_code |= ERR_WARN;
4749
4750 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4751 goto out;
4752
4753 curpx->options2 &= ~PR_O2_CHK_ANY;
4754 curpx->options2 |= PR_O2_TCPCHK_CHK;
4755
4756 free_tcpcheck_vars(&rules->preset_vars);
4757 rules->list = NULL;
4758 rules->flags = 0;
4759
4760 rs = find_tcpcheck_ruleset("*ldap-check");
4761 if (rs)
4762 goto ruleset_found;
4763
4764 rs = create_tcpcheck_ruleset("*ldap-check");
4765 if (rs == NULL) {
4766 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4767 goto error;
4768 }
4769
4770 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4771 1, curpx, &rs->rules, file, line, &errmsg);
4772 if (!chk) {
4773 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4774 goto error;
4775 }
4776 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004777 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004778
4779 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4780 "min-recv", "14",
4781 "on-error", "Not LDAPv3 protocol",
4782 ""},
4783 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4784 if (!chk) {
4785 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4786 goto error;
4787 }
4788 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004789 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004790
4791 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4792 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4793 if (!chk) {
4794 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4795 goto error;
4796 }
4797 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4798 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004799 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004800
4801 ruleset_found:
4802 rules->list = &rs->rules;
4803 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4804 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4805
4806 out:
4807 free(errmsg);
4808 return err_code;
4809
4810 error:
4811 free_tcpcheck_ruleset(rs);
4812 err_code |= ERR_ALERT | ERR_FATAL;
4813 goto out;
4814}
4815
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004816int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004817 const char *file, int line)
4818{
4819 struct tcpcheck_ruleset *rs = NULL;
4820 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4821 struct tcpcheck_rule *chk;
4822 char *spop_req = NULL;
4823 char *errmsg = NULL;
4824 int spop_len = 0, err_code = 0;
4825
4826 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4827 err_code |= ERR_WARN;
4828
4829 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4830 goto out;
4831
4832 curpx->options2 &= ~PR_O2_CHK_ANY;
4833 curpx->options2 |= PR_O2_TCPCHK_CHK;
4834
4835 free_tcpcheck_vars(&rules->preset_vars);
4836 rules->list = NULL;
4837 rules->flags = 0;
4838
4839
4840 rs = find_tcpcheck_ruleset("*spop-check");
4841 if (rs)
4842 goto ruleset_found;
4843
4844 rs = create_tcpcheck_ruleset("*spop-check");
4845 if (rs == NULL) {
4846 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4847 goto error;
4848 }
4849
4850 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4851 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4852 goto error;
4853 }
4854 chunk_reset(&trash);
4855 dump_binary(&trash, spop_req, spop_len);
4856 trash.area[trash.data] = '\0';
4857
4858 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4859 1, curpx, &rs->rules, file, line, &errmsg);
4860 if (!chk) {
4861 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4862 goto error;
4863 }
4864 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004865 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004866
4867 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4868 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4869 if (!chk) {
4870 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4871 goto error;
4872 }
4873 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4874 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004875 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004876
4877 ruleset_found:
4878 rules->list = &rs->rules;
4879 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4880 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4881
4882 out:
4883 free(spop_req);
4884 free(errmsg);
4885 return err_code;
4886
4887 error:
4888 free_tcpcheck_ruleset(rs);
4889 err_code |= ERR_ALERT | ERR_FATAL;
4890 goto out;
4891}
4892
4893
4894static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4895{
4896 struct tcpcheck_rule *chk = NULL;
4897 struct tcpcheck_http_hdr *hdr = NULL;
4898 char *meth = NULL, *uri = NULL, *vsn = NULL;
4899 char *hdrs, *body;
4900
4901 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4902 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4903 if (hdrs == body)
4904 hdrs = NULL;
4905 if (hdrs) {
4906 *hdrs = '\0';
4907 hdrs +=2;
4908 }
4909 if (body) {
4910 *body = '\0';
4911 body += 4;
4912 }
4913 if (hdrs || body) {
4914 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4915 " Please, consider to use 'http-check send' directive instead.");
4916 }
4917
4918 chk = calloc(1, sizeof(*chk));
4919 if (!chk) {
4920 memprintf(errmsg, "out of memory");
4921 goto error;
4922 }
4923 chk->action = TCPCHK_ACT_SEND;
4924 chk->send.type = TCPCHK_SEND_HTTP;
4925 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4926 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4927 LIST_INIT(&chk->send.http.hdrs);
4928
4929 /* Copy the method, uri and version */
4930 if (*args[cur_arg]) {
4931 if (!*args[cur_arg+1])
4932 uri = args[cur_arg];
4933 else
4934 meth = args[cur_arg];
4935 }
4936 if (*args[cur_arg+1])
4937 uri = args[cur_arg+1];
4938 if (*args[cur_arg+2])
4939 vsn = args[cur_arg+2];
4940
4941 if (meth) {
4942 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4943 chk->send.http.meth.str.area = strdup(meth);
4944 chk->send.http.meth.str.data = strlen(meth);
4945 if (!chk->send.http.meth.str.area) {
4946 memprintf(errmsg, "out of memory");
4947 goto error;
4948 }
4949 }
4950 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004951 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004952 if (!isttest(chk->send.http.uri)) {
4953 memprintf(errmsg, "out of memory");
4954 goto error;
4955 }
4956 }
4957 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004958 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004959 if (!isttest(chk->send.http.vsn)) {
4960 memprintf(errmsg, "out of memory");
4961 goto error;
4962 }
4963 }
4964
4965 /* Copy the header */
4966 if (hdrs) {
4967 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4968 struct h1m h1m;
4969 int i, ret;
4970
4971 /* Build and parse the request */
4972 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4973
4974 h1m.flags = H1_MF_HDRS_ONLY;
4975 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4976 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4977 &h1m, NULL);
4978 if (ret <= 0) {
4979 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4980 goto error;
4981 }
4982
4983 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4984 hdr = calloc(1, sizeof(*hdr));
4985 if (!hdr) {
4986 memprintf(errmsg, "out of memory");
4987 goto error;
4988 }
4989 LIST_INIT(&hdr->value);
4990 hdr->name = istdup(tmp_hdrs[i].n);
4991 if (!hdr->name.ptr) {
4992 memprintf(errmsg, "out of memory");
4993 goto error;
4994 }
4995
4996 ist0(tmp_hdrs[i].v);
4997 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
4998 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02004999 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005000 }
5001 }
5002
5003 /* Copy the body */
5004 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005005 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005006 if (!isttest(chk->send.http.body)) {
5007 memprintf(errmsg, "out of memory");
5008 goto error;
5009 }
5010 }
5011
5012 return chk;
5013
5014 error:
5015 free_tcpcheck_http_hdr(hdr);
5016 free_tcpcheck(chk, 0);
5017 return NULL;
5018}
5019
5020/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005021int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005022 const char *file, int line)
5023{
5024 struct tcpcheck_ruleset *rs = NULL;
5025 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5026 struct tcpcheck_rule *chk;
5027 char *errmsg = NULL;
5028 int err_code = 0;
5029
5030 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5031 err_code |= ERR_WARN;
5032
5033 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5034 goto out;
5035
5036 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5037 if (!chk) {
5038 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5039 goto error;
5040 }
5041 if (errmsg) {
5042 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5043 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005044 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005045 }
5046
5047 no_request:
5048 curpx->options2 &= ~PR_O2_CHK_ANY;
5049 curpx->options2 |= PR_O2_TCPCHK_CHK;
5050
5051 free_tcpcheck_vars(&rules->preset_vars);
5052 rules->list = NULL;
5053 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5054
5055 /* Deduce the ruleset name from the proxy info */
5056 chunk_printf(&trash, "*http-check-%s_%s-%d",
5057 ((curpx == defpx) ? "defaults" : curpx->id),
5058 curpx->conf.file, curpx->conf.line);
5059
5060 rs = find_tcpcheck_ruleset(b_orig(&trash));
5061 if (rs == NULL) {
5062 rs = create_tcpcheck_ruleset(b_orig(&trash));
5063 if (rs == NULL) {
5064 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5065 goto error;
5066 }
5067 }
5068
5069 rules->list = &rs->rules;
5070 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5071 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5072 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5073 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5074 rules->list = NULL;
5075 goto error;
5076 }
5077
5078 out:
5079 free(errmsg);
5080 return err_code;
5081
5082 error:
5083 free_tcpcheck_ruleset(rs);
5084 free_tcpcheck(chk, 0);
5085 err_code |= ERR_ALERT | ERR_FATAL;
5086 goto out;
5087}
5088
5089/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005090int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005091 const char *file, int line)
5092{
5093 struct tcpcheck_ruleset *rs = NULL;
5094 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5095 int err_code = 0;
5096
5097 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5098 err_code |= ERR_WARN;
5099
5100 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5101 goto out;
5102
5103 curpx->options2 &= ~PR_O2_CHK_ANY;
5104 curpx->options2 |= PR_O2_TCPCHK_CHK;
5105
5106 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5107 /* If a tcp-check rulesset is already set, do nothing */
5108 if (rules->list)
5109 goto out;
5110
5111 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5112 * get it.
5113 */
5114 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5115 goto curpx_ruleset;
5116
5117 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5118 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5119 rs = find_tcpcheck_ruleset(b_orig(&trash));
5120 if (rs)
5121 goto ruleset_found;
5122 }
5123
5124 curpx_ruleset:
5125 /* Deduce the ruleset name from the proxy info */
5126 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5127 ((curpx == defpx) ? "defaults" : curpx->id),
5128 curpx->conf.file, curpx->conf.line);
5129
5130 rs = find_tcpcheck_ruleset(b_orig(&trash));
5131 if (rs == NULL) {
5132 rs = create_tcpcheck_ruleset(b_orig(&trash));
5133 if (rs == NULL) {
5134 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5135 goto error;
5136 }
5137 }
5138
5139 ruleset_found:
5140 free_tcpcheck_vars(&rules->preset_vars);
5141 rules->list = &rs->rules;
5142 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5143 rules->flags |= TCPCHK_RULES_TCP_CHK;
5144
5145 out:
5146 return err_code;
5147
5148 error:
5149 err_code |= ERR_ALERT | ERR_FATAL;
5150 goto out;
5151}
5152
Willy Tarreau51cd5952020-06-05 12:25:38 +02005153static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005154 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005155 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5156 { 0, NULL, NULL },
5157}};
5158
5159REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5160REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5161REGISTER_POST_DEINIT(deinit_tcpchecks);
5162INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);