blob: d1f9a155dabc5674f4574c315c2d599ef1ee8bd4 [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>
Willy Tarreau36979d92020-06-05 17:27:29 +020042#include <haproxy/errors.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020043#include <haproxy/global.h>
44#include <haproxy/h1.h>
45#include <haproxy/http.h>
46#include <haproxy/http_htx.h>
47#include <haproxy/htx.h>
48#include <haproxy/istbuf.h>
49#include <haproxy/list.h>
50#include <haproxy/log.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020051#include <haproxy/protocol.h>
52#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020053#include <haproxy/regex.h>
54#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020055#include <haproxy/server.h>
56#include <haproxy/ssl_sock.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020057#include <haproxy/task.h>
58#include <haproxy/tcpcheck.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020059#include <haproxy/time.h>
60#include <haproxy/tools.h>
Christopher Faulet147b8c92021-04-10 09:00:38 +020061#include <haproxy/trace.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020062#include <haproxy/vars.h>
63
64
Christopher Faulet147b8c92021-04-10 09:00:38 +020065#define TRACE_SOURCE &trace_check
66
Willy Tarreau51cd5952020-06-05 12:25:38 +020067/* Global tree to share all tcp-checks */
68struct eb_root shared_tcpchecks = EB_ROOT;
69
70
71DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
72
73/**************************************************************************/
74/*************** Init/deinit tcp-check rules and ruleset ******************/
75/**************************************************************************/
76/* Releases memory allocated for a log-format string */
77static void free_tcpcheck_fmt(struct list *fmt)
78{
79 struct logformat_node *lf, *lfb;
80
81 list_for_each_entry_safe(lf, lfb, fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +020082 LIST_DELETE(&lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +020083 release_sample_expr(lf->expr);
84 free(lf->arg);
85 free(lf);
86 }
87}
88
89/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
90void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
91{
92 if (!hdr)
93 return;
94
95 free_tcpcheck_fmt(&hdr->value);
96 istfree(&hdr->name);
97 free(hdr);
98}
99
100/* Releases memory allocated for an HTTP header list used in a tcp-check send
101 * rule
102 */
103static void free_tcpcheck_http_hdrs(struct list *hdrs)
104{
105 struct tcpcheck_http_hdr *hdr, *bhdr;
106
107 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200108 LIST_DELETE(&hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200109 free_tcpcheck_http_hdr(hdr);
110 }
111}
112
113/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
114 * tcp-check was allocated using a memory pool (it is used to instantiate email
115 * alerts).
116 */
117void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
118{
119 if (!rule)
120 return;
121
122 free(rule->comment);
123 switch (rule->action) {
124 case TCPCHK_ACT_SEND:
125 switch (rule->send.type) {
126 case TCPCHK_SEND_STRING:
127 case TCPCHK_SEND_BINARY:
128 istfree(&rule->send.data);
129 break;
130 case TCPCHK_SEND_STRING_LF:
131 case TCPCHK_SEND_BINARY_LF:
132 free_tcpcheck_fmt(&rule->send.fmt);
133 break;
134 case TCPCHK_SEND_HTTP:
135 free(rule->send.http.meth.str.area);
136 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
137 istfree(&rule->send.http.uri);
138 else
139 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
140 istfree(&rule->send.http.vsn);
141 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
142 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
143 istfree(&rule->send.http.body);
144 else
145 free_tcpcheck_fmt(&rule->send.http.body_fmt);
146 break;
147 case TCPCHK_SEND_UNDEF:
148 break;
149 }
150 break;
151 case TCPCHK_ACT_EXPECT:
152 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
153 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
154 release_sample_expr(rule->expect.status_expr);
155 switch (rule->expect.type) {
156 case TCPCHK_EXPECT_HTTP_STATUS:
157 free(rule->expect.codes.codes);
158 break;
159 case TCPCHK_EXPECT_STRING:
160 case TCPCHK_EXPECT_BINARY:
161 case TCPCHK_EXPECT_HTTP_BODY:
162 istfree(&rule->expect.data);
163 break;
164 case TCPCHK_EXPECT_STRING_REGEX:
165 case TCPCHK_EXPECT_BINARY_REGEX:
166 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
167 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
168 regex_free(rule->expect.regex);
169 break;
170 case TCPCHK_EXPECT_STRING_LF:
171 case TCPCHK_EXPECT_BINARY_LF:
172 case TCPCHK_EXPECT_HTTP_BODY_LF:
173 free_tcpcheck_fmt(&rule->expect.fmt);
174 break;
175 case TCPCHK_EXPECT_HTTP_HEADER:
176 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
177 regex_free(rule->expect.hdr.name_re);
178 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
179 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
180 else
181 istfree(&rule->expect.hdr.name);
182
183 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
184 regex_free(rule->expect.hdr.value_re);
185 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
186 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
187 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
188 istfree(&rule->expect.hdr.value);
189 break;
190 case TCPCHK_EXPECT_CUSTOM:
191 case TCPCHK_EXPECT_UNDEF:
192 break;
193 }
194 break;
195 case TCPCHK_ACT_CONNECT:
196 free(rule->connect.sni);
197 free(rule->connect.alpn);
198 release_sample_expr(rule->connect.port_expr);
199 break;
200 case TCPCHK_ACT_COMMENT:
201 break;
202 case TCPCHK_ACT_ACTION_KW:
203 free(rule->action_kw.rule);
204 break;
205 }
206
207 if (in_pool)
208 pool_free(pool_head_tcpcheck_rule, rule);
209 else
210 free(rule);
211}
212
213/* Creates a tcp-check variable used in preset variables before executing a
214 * tcp-check ruleset.
215 */
216struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
217{
218 struct tcpcheck_var *var = NULL;
219
220 var = calloc(1, sizeof(*var));
221 if (var == NULL)
222 return NULL;
223
224 var->name = istdup(name);
225 if (!isttest(var->name)) {
226 free(var);
227 return NULL;
228 }
229
230 LIST_INIT(&var->list);
231 return var;
232}
233
234/* Releases memory allocated for a preset tcp-check variable */
235void free_tcpcheck_var(struct tcpcheck_var *var)
236{
237 if (!var)
238 return;
239
240 istfree(&var->name);
241 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
242 free(var->data.u.str.area);
243 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
244 free(var->data.u.meth.str.area);
245 free(var);
246}
247
248/* Releases a list of preset tcp-check variables */
249void free_tcpcheck_vars(struct list *vars)
250{
251 struct tcpcheck_var *var, *back;
252
253 list_for_each_entry_safe(var, back, vars, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200254 LIST_DELETE(&var->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200255 free_tcpcheck_var(var);
256 }
257}
258
259/* Duplicate a list of preset tcp-check variables */
Willy Tarreau09f2e772021-02-12 08:42:30 +0100260int dup_tcpcheck_vars(struct list *dst, const struct list *src)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200261{
Willy Tarreau09f2e772021-02-12 08:42:30 +0100262 const struct tcpcheck_var *var;
263 struct tcpcheck_var *new = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200264
265 list_for_each_entry(var, src, list) {
266 new = create_tcpcheck_var(var->name);
267 if (!new)
268 goto error;
269 new->data.type = var->data.type;
270 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
271 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
272 goto error;
273 if (var->data.type == SMP_T_STR)
274 new->data.u.str.area[new->data.u.str.data] = 0;
275 }
276 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
277 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
278 goto error;
279 new->data.u.str.area[new->data.u.str.data] = 0;
280 new->data.u.meth.meth = var->data.u.meth.meth;
281 }
282 else
283 new->data.u = var->data.u;
Willy Tarreau2b718102021-04-21 07:32:39 +0200284 LIST_APPEND(dst, &new->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200285 }
286 return 1;
287
288 error:
289 free(new);
290 return 0;
291}
292
293/* Looks for a shared tcp-check ruleset given its name. */
294struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
295{
296 struct tcpcheck_ruleset *rs;
297 struct ebpt_node *node;
298
299 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
300 if (node) {
301 rs = container_of(node, typeof(*rs), node);
302 return rs;
303 }
304 return NULL;
305}
306
307/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks
308 * tree.
309 */
310struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
311{
312 struct tcpcheck_ruleset *rs;
313
314 rs = calloc(1, sizeof(*rs));
315 if (rs == NULL)
316 return NULL;
317
318 rs->node.key = strdup(name);
319 if (rs->node.key == NULL) {
320 free(rs);
321 return NULL;
322 }
323
324 LIST_INIT(&rs->rules);
325 ebis_insert(&shared_tcpchecks, &rs->node);
326 return rs;
327}
328
329/* Releases memory allocated by a tcp-check ruleset. */
330void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
331{
332 struct tcpcheck_rule *r, *rb;
333
334 if (!rs)
335 return;
336
337 ebpt_delete(&rs->node);
338 free(rs->node.key);
339 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200340 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200341 free_tcpcheck(r, 0);
342 }
343 free(rs);
344}
345
346
347/**************************************************************************/
348/**************** Everything about tcp-checks execution *******************/
349/**************************************************************************/
350/* Returns the id of a step in a tcp-check ruleset */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200351int tcpcheck_get_step_id(const struct check *check, const struct tcpcheck_rule *rule)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200352{
353 if (!rule)
354 rule = check->current_step;
355
356 /* no last started step => first step */
357 if (!rule)
358 return 1;
359
360 /* last step is the first implicit connect */
361 if (rule->index == 0 &&
362 rule->action == TCPCHK_ACT_CONNECT &&
363 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
364 return 0;
365
366 return rule->index + 1;
367}
368
369/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
370 * NULL if none was found.
371 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200372struct tcpcheck_rule *get_first_tcpcheck_rule(const struct tcpcheck_rules *rules)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200373{
374 struct tcpcheck_rule *r;
375
376 list_for_each_entry(r, rules->list, list) {
377 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
378 return r;
379 }
380 return NULL;
381}
382
383/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
384 * NULL if none was found.
385 */
386static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
387{
388 struct tcpcheck_rule *r;
389
390 list_for_each_entry_rev(r, rules->list, list) {
391 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
392 return r;
393 }
394 return NULL;
395}
396
397/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
398 * <start> or NULL if non was found. If <start> is NULL, it relies on
399 * get_first_tcpcheck_rule().
400 */
401static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
402{
403 struct tcpcheck_rule *r;
404
405 if (!start)
406 return get_first_tcpcheck_rule(rules);
407
408 r = LIST_NEXT(&start->list, typeof(r), list);
409 list_for_each_entry_from(r, rules->list, list) {
410 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
411 return r;
412 }
413 return NULL;
414}
415
416
417/* Creates info message when a tcp-check healthcheck fails on an expect rule */
418static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
419 int match, struct ist info)
420{
421 struct sample *smp;
422
423 /* Follows these step to produce the info message:
424 * 1. if info field is already provided, copy it
425 * 2. if the expect rule provides an onerror log-format string,
426 * use it to produce the message
427 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
428 * 4. Otherwise produce the generic tcp-check info message
429 */
430 if (istlen(info)) {
431 chunk_strncat(msg, istptr(info), istlen(info));
432 goto comment;
433 }
434 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
435 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
436 goto comment;
437 }
438
439 if (check->type == PR_O2_TCPCHK_CHK &&
440 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
441 goto comment;
442
443 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
444 switch (rule->expect.type) {
445 case TCPCHK_EXPECT_HTTP_STATUS:
446 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
447 break;
448 case TCPCHK_EXPECT_STRING:
449 case TCPCHK_EXPECT_HTTP_BODY:
450 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
451 tcpcheck_get_step_id(check, rule));
452 break;
453 case TCPCHK_EXPECT_BINARY:
454 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
455 break;
456 case TCPCHK_EXPECT_STRING_REGEX:
457 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
458 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
459 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
460 break;
461 case TCPCHK_EXPECT_BINARY_REGEX:
462 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
463 break;
464 case TCPCHK_EXPECT_STRING_LF:
465 case TCPCHK_EXPECT_HTTP_BODY_LF:
466 chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule));
467 break;
468 case TCPCHK_EXPECT_BINARY_LF:
469 chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule));
470 break;
471 case TCPCHK_EXPECT_CUSTOM:
472 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
473 break;
474 case TCPCHK_EXPECT_HTTP_HEADER:
475 chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
476 case TCPCHK_EXPECT_UNDEF:
477 /* Should never happen. */
478 return;
479 }
480
481 comment:
482 /* If the failing expect rule provides a comment, it is concatenated to
483 * the info message.
484 */
485 if (rule->comment) {
486 chunk_strcat(msg, " comment: ");
487 chunk_strcat(msg, rule->comment);
488 }
489
490 /* Finally, the check status code is set if the failing expect rule
491 * defines a status expression.
492 */
493 if (rule->expect.status_expr) {
494 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
495 rule->expect.status_expr, SMP_T_STR);
496
497 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
498 sample_casts[smp->data.type][SMP_T_SINT](smp))
499 check->code = smp->data.u.sint;
500 }
501
502 *(b_tail(msg)) = '\0';
503}
504
505/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
506static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
507 struct ist info)
508{
509 struct sample *smp;
510
511 /* Follows these step to produce the info message:
512 * 1. if info field is already provided, copy it
513 * 2. if the expect rule provides an onsucces log-format string,
514 * use it to produce the message
515 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
516 * 4. Otherwise produce the generic tcp-check info message
517 */
518 if (istlen(info))
519 chunk_strncat(msg, istptr(info), istlen(info));
520 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
521 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
522 &rule->expect.onsuccess_fmt);
523 else if (check->type == PR_O2_TCPCHK_CHK &&
524 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
525 chunk_strcat(msg, "(tcp-check)");
526
527 /* Finally, the check status code is set if the expect rule defines a
528 * status expression.
529 */
530 if (rule->expect.status_expr) {
531 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
532 rule->expect.status_expr, SMP_T_STR);
533
534 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
535 sample_casts[smp->data.type][SMP_T_SINT](smp))
536 check->code = smp->data.u.sint;
537 }
538
539 *(b_tail(msg)) = '\0';
540}
541
542/* Internal functions to parse and validate a MySQL packet in the context of an
543 * expect rule. It start to parse the input buffer at the offset <offset>. If
544 * <last_read> is set, no more data are expected.
545 */
546static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
547 unsigned int offset, int last_read)
548{
549 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
550 enum healthcheck_status status;
551 struct buffer *msg = NULL;
552 struct ist desc = IST_NULL;
553 unsigned int err = 0, plen = 0;
554
555
Christopher Faulet147b8c92021-04-10 09:00:38 +0200556 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
557
Willy Tarreau51cd5952020-06-05 12:25:38 +0200558 /* 3 Bytes for the packet length and 1 byte for the sequence id */
559 if (b_data(&check->bi) < offset+4) {
560 if (!last_read)
561 goto wait_more_data;
562
563 /* invalid length or truncated response */
564 status = HCHK_STATUS_L7RSP;
565 goto error;
566 }
567
568 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
569 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
570 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
571
572 if (b_data(&check->bi) < offset+plen+4) {
573 if (!last_read)
574 goto wait_more_data;
575
576 /* invalid length or truncated response */
577 status = HCHK_STATUS_L7RSP;
578 goto error;
579 }
580
581 if (*b_peek(&check->bi, offset+4) == '\xff') {
582 /* MySQL Error packet always begin with field_count = 0xff */
583 status = HCHK_STATUS_L7STS;
584 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
585 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
586 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
587 goto error;
588 }
589
590 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
591 /* Not the last rule, continue */
592 goto out;
593 }
594
595 /* We set the MySQL Version in description for information purpose
596 * FIXME : it can be cool to use MySQL Version for other purpose,
597 * like mark as down old MySQL server.
598 */
599 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
600 set_server_check_status(check, status, b_peek(&check->bi, 5));
601
602 out:
603 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200604 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200605 return ret;
606
607 error:
608 ret = TCPCHK_EVAL_STOP;
609 check->code = err;
610 msg = alloc_trash_chunk();
611 if (msg)
612 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
613 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
614 goto out;
615
616 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200617 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200618 ret = TCPCHK_EVAL_WAIT;
619 goto out;
620}
621
622/* Custom tcp-check expect function to parse and validate the MySQL initial
623 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
624 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
625 * error occurred.
626 */
627enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
628{
629 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
630}
631
632/* Custom tcp-check expect function to parse and validate the MySQL OK packet
633 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
634 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
635 * an error occurred.
636 */
637enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
638{
639 unsigned int hslen = 0;
640
641 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
642 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
643 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
644
645 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
646}
647
648/* Custom tcp-check expect function to parse and validate the LDAP bind response
649 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
650 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
651 * error occurred.
652 */
653enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
654{
655 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
656 enum healthcheck_status status;
657 struct buffer *msg = NULL;
658 struct ist desc = IST_NULL;
659 unsigned short msglen = 0;
660
Christopher Faulet147b8c92021-04-10 09:00:38 +0200661 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
662
Willy Tarreau51cd5952020-06-05 12:25:38 +0200663 /* Check if the server speaks LDAP (ASN.1/BER)
664 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
665 * http://tools.ietf.org/html/rfc4511
666 */
667 /* size of LDAPMessage */
668 msglen = (*(b_head(&check->bi) + 1) & 0x80) ? (*(b_head(&check->bi) + 1) & 0x7f) : 0;
669
670 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
671 * messageID: 0x02 0x01 0x01: INTEGER 1
672 * protocolOp: 0x61: bindResponse
673 */
674 if ((msglen > 2) || (memcmp(b_head(&check->bi) + 2 + msglen, "\x02\x01\x01\x61", 4) != 0)) {
675 status = HCHK_STATUS_L7RSP;
676 desc = ist("Not LDAPv3 protocol");
677 goto error;
678 }
679
680 /* size of bindResponse */
681 msglen += (*(b_head(&check->bi) + msglen + 6) & 0x80) ? (*(b_head(&check->bi) + msglen + 6) & 0x7f) : 0;
682
683 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
684 * ldapResult: 0x0a 0x01: ENUMERATION
685 */
686 if ((msglen > 4) || (memcmp(b_head(&check->bi) + 7 + msglen, "\x0a\x01", 2) != 0)) {
687 status = HCHK_STATUS_L7RSP;
688 desc = ist("Not LDAPv3 protocol");
689 goto error;
690 }
691
692 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
693 * resultCode
694 */
695 check->code = *(b_head(&check->bi) + msglen + 9);
696 if (check->code) {
697 status = HCHK_STATUS_L7STS;
698 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
699 goto error;
700 }
701
702 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
703 set_server_check_status(check, status, "Success");
704
705 out:
706 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200707 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200708 return ret;
709
710 error:
711 ret = TCPCHK_EVAL_STOP;
712 msg = alloc_trash_chunk();
713 if (msg)
714 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
715 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
716 goto out;
717}
718
719/* Custom tcp-check expect function to parse and validate the SPOP hello agent
720 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
721 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
722 */
723enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
724{
725 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
726 enum healthcheck_status status;
727 struct buffer *msg = NULL;
728 struct ist desc = IST_NULL;
729 unsigned int framesz;
730
Christopher Faulet147b8c92021-04-10 09:00:38 +0200731 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200732
733 memcpy(&framesz, b_head(&check->bi), 4);
734 framesz = ntohl(framesz);
735
736 if (!last_read && b_data(&check->bi) < (4+framesz))
737 goto wait_more_data;
738
739 memset(b_orig(&trash), 0, b_size(&trash));
740 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
741 status = HCHK_STATUS_L7RSP;
742 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
743 goto error;
744 }
745
746 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
747 set_server_check_status(check, status, "SPOA server is ok");
748
749 out:
750 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200751 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200752 return ret;
753
754 error:
755 ret = TCPCHK_EVAL_STOP;
756 msg = alloc_trash_chunk();
757 if (msg)
758 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
759 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
760 goto out;
761
762 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200763 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200764 ret = TCPCHK_EVAL_WAIT;
765 goto out;
766}
767
768/* Custom tcp-check expect function to parse and validate the agent-check
769 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
770 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
771 */
772enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
773{
774 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
775 enum healthcheck_status status = HCHK_STATUS_CHECKED;
776 const char *hs = NULL; /* health status */
777 const char *as = NULL; /* admin status */
778 const char *ps = NULL; /* performance status */
779 const char *cs = NULL; /* maxconn */
780 const char *err = NULL; /* first error to report */
781 const char *wrn = NULL; /* first warning to report */
782 char *cmd, *p;
783
Christopher Faulet147b8c92021-04-10 09:00:38 +0200784 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
785
Willy Tarreau51cd5952020-06-05 12:25:38 +0200786 /* We're getting an agent check response. The agent could
787 * have been disabled in the mean time with a long check
788 * still pending. It is important that we ignore the whole
789 * response.
790 */
791 if (!(check->state & CHK_ST_ENABLED))
792 goto out;
793
794 /* The agent supports strings made of a single line ended by the
795 * first CR ('\r') or LF ('\n'). This line is composed of words
796 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
797 * line may optionally contained a description of a state change
798 * after a sharp ('#'), which is only considered if a health state
799 * is announced.
800 *
801 * Words may be composed of :
802 * - a numeric weight suffixed by the percent character ('%').
803 * - a health status among "up", "down", "stopped", and "fail".
804 * - an admin status among "ready", "drain", "maint".
805 *
806 * These words may appear in any order. If multiple words of the
807 * same category appear, the last one wins.
808 */
809
810 p = b_head(&check->bi);
811 while (*p && *p != '\n' && *p != '\r')
812 p++;
813
814 if (!*p) {
815 if (!last_read)
816 goto wait_more_data;
817
818 /* at least inform the admin that the agent is mis-behaving */
819 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
820 goto out;
821 }
822
823 *p = 0;
824 cmd = b_head(&check->bi);
825
826 while (*cmd) {
827 /* look for next word */
828 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
829 cmd++;
830 continue;
831 }
832
833 if (*cmd == '#') {
834 /* this is the beginning of a health status description,
835 * skip the sharp and blanks.
836 */
837 cmd++;
838 while (*cmd == '\t' || *cmd == ' ')
839 cmd++;
840 break;
841 }
842
843 /* find the end of the word so that we have a null-terminated
844 * word between <cmd> and <p>.
845 */
846 p = cmd + 1;
847 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
848 p++;
849 if (*p)
850 *p++ = 0;
851
852 /* first, health statuses */
853 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100854 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200855 status = HCHK_STATUS_L7OKD;
856 hs = cmd;
857 }
858 else if (strcasecmp(cmd, "down") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100859 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200860 status = HCHK_STATUS_L7STS;
861 hs = cmd;
862 }
863 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100864 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200865 status = HCHK_STATUS_L7STS;
866 hs = cmd;
867 }
868 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100869 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200870 status = HCHK_STATUS_L7STS;
871 hs = cmd;
872 }
873 /* admin statuses */
874 else if (strcasecmp(cmd, "ready") == 0) {
875 as = cmd;
876 }
877 else if (strcasecmp(cmd, "drain") == 0) {
878 as = cmd;
879 }
880 else if (strcasecmp(cmd, "maint") == 0) {
881 as = cmd;
882 }
883 /* try to parse a weight here and keep the last one */
884 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
885 ps = cmd;
886 }
887 /* try to parse a maxconn here */
888 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
889 cs = cmd;
890 }
891 else {
892 /* keep a copy of the first error */
893 if (!err)
894 err = cmd;
895 }
896 /* skip to next word */
897 cmd = p;
898 }
899 /* here, cmd points either to \0 or to the beginning of a
900 * description. Skip possible leading spaces.
901 */
902 while (*cmd == ' ' || *cmd == '\n')
903 cmd++;
904
905 /* First, update the admin status so that we avoid sending other
906 * possibly useless warnings and can also update the health if
907 * present after going back up.
908 */
909 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200910 if (strcasecmp(as, "drain") == 0) {
911 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200912 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200913 }
914 else if (strcasecmp(as, "maint") == 0) {
915 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200916 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200917 }
918 else {
919 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200920 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200921 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200922 }
923
924 /* now change weights */
925 if (ps) {
926 const char *msg;
927
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500928 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200929 msg = server_parse_weight_change_request(check->server, ps);
930 if (!wrn || !*wrn)
931 wrn = msg;
932 }
933
934 if (cs) {
935 const char *msg;
936
937 cs += strlen("maxconn:");
938
Christopher Faulet147b8c92021-04-10 09:00:38 +0200939 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200940 msg = server_parse_maxconn_change_request(check->server, cs);
941 if (!wrn || !*wrn)
942 wrn = msg;
943 }
944
945 /* and finally health status */
946 if (hs) {
947 /* We'll report some of the warnings and errors we have
948 * here. Down reports are critical, we leave them untouched.
949 * Lack of report, or report of 'UP' leaves the room for
950 * ERR first, then WARN.
951 */
952 const char *msg = cmd;
953 struct buffer *t;
954
955 if (!*msg || status == HCHK_STATUS_L7OKD) {
956 if (err && *err)
957 msg = err;
958 else if (wrn && *wrn)
959 msg = wrn;
960 }
961
962 t = get_trash_chunk();
963 chunk_printf(t, "via agent : %s%s%s%s",
964 hs, *msg ? " (" : "",
965 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +0200966 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200967 set_server_check_status(check, status, t->area);
968 }
969 else if (err && *err) {
970 /* No status change but we'd like to report something odd.
971 * Just report the current state and copy the message.
972 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200973 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200974 chunk_printf(&trash, "agent reports an error : %s", err);
975 set_server_check_status(check, status/*check->status*/, trash.area);
976 }
977 else if (wrn && *wrn) {
978 /* No status change but we'd like to report something odd.
979 * Just report the current state and copy the message.
980 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200981 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200982 chunk_printf(&trash, "agent warns : %s", wrn);
983 set_server_check_status(check, status/*check->status*/, trash.area);
984 }
Christopher Faulet147b8c92021-04-10 09:00:38 +0200985 else {
986 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200987 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200988 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200989
990 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200991 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200992 return ret;
993
994 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200995 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200996 ret = TCPCHK_EVAL_WAIT;
997 goto out;
998}
999
1000/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1001 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1002 * TCPCHK_EVAL_STOP if an error occurred.
1003 */
1004enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1005{
1006 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1007 struct tcpcheck_connect *connect = &rule->connect;
1008 struct proxy *proxy = check->proxy;
1009 struct server *s = check->server;
1010 struct task *t = check->task;
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001011 struct conn_stream *cs = check->cs;
1012 struct connection *conn = cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001013 struct protocol *proto;
1014 struct xprt_ops *xprt;
1015 struct tcpcheck_rule *next;
1016 int status, port;
1017
Christopher Faulet147b8c92021-04-10 09:00:38 +02001018 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1019
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001020 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1021
1022 /* current connection already created, check if it is established or not */
1023 if (conn) {
1024 if (conn->flags & CO_FL_WAIT_XPRT) {
1025 /* We are still waiting for the connection establishment */
1026 if (next && next->action == TCPCHK_ACT_SEND) {
1027 if (!(check->wait_list.events & SUB_RETRY_SEND))
1028 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1029 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001030 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001031 }
1032 else
1033 ret = tcpcheck_eval_recv(check, rule);
1034 }
1035 goto out;
1036 }
1037
1038 /* Note: here check->cs = cs = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001039
Christopher Fauletb381a502020-11-25 13:47:00 +01001040 /* Always release input and output buffer when a new connect is evaluated */
1041 check_release_buf(check, &check->bi);
1042 check_release_buf(check, &check->bo);
1043
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001044 /* No connection, prepare a new one */
Christopher Faulet236c93b2020-07-02 09:19:54 +02001045 cs = cs_new(NULL, (s ? &s->obj_type : &proxy->obj_type));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001046 if (!cs) {
1047 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1048 tcpcheck_get_step_id(check, rule));
1049 if (rule->comment)
1050 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1051 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1052 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001053 TRACE_ERROR("conn-stream allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001054 goto out;
1055 }
1056
Willy Tarreau51cd5952020-06-05 12:25:38 +02001057 tasklet_set_tid(check->wait_list.tasklet, tid);
1058
1059 check->cs = cs;
1060 conn = cs->conn;
1061 conn_set_owner(conn, check->sess, NULL);
1062
1063 /* Maybe there were an older connection we were waiting on */
1064 check->wait_list.events = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001065
1066 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001067 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001068 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001069 status = SF_ERR_RESOURCE;
1070 goto fail_check;
1071 }
1072
1073 /* connect to the connect rule addr if specified, otherwise the check
1074 * addr if specified on the server. otherwise, use the server addr (it
1075 * MUST exist at this step).
1076 */
1077 *conn->dst = (is_addr(&connect->addr)
1078 ? connect->addr
1079 : (is_addr(&check->addr) ? check->addr : s->addr));
1080 proto = protocol_by_family(conn->dst->ss_family);
1081
1082 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001083 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001084 port = connect->port;
1085 if (!port && connect->port_expr) {
1086 struct sample *smp;
1087
1088 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1089 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1090 connect->port_expr, SMP_T_SINT);
1091 if (smp)
1092 port = smp->data.u.sint;
1093 }
1094 if (!port && is_inet_addr(&connect->addr))
1095 port = get_host_port(&connect->addr);
1096 if (!port && check->port)
1097 port = check->port;
1098 if (!port && is_inet_addr(&check->addr))
1099 port = get_host_port(&check->addr);
1100 if (!port) {
1101 /* The server MUST exist here */
1102 port = s->svc_port;
1103 }
1104 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001105 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001106
1107 xprt = ((connect->options & TCPCHK_OPT_SSL)
1108 ? xprt_get(XPRT_SSL)
1109 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1110
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001111 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001112 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001113 status = SF_ERR_RESOURCE;
1114 goto fail_check;
1115 }
1116
Willy Tarreau51cd5952020-06-05 12:25:38 +02001117 cs_attach(cs, check, &check_conn_cb);
1118
Christopher Fauletf7177272020-10-02 13:41:55 +02001119 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1120 conn->send_proxy_ofs = 1;
1121 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001122 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001123 }
1124 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1125 conn->send_proxy_ofs = 1;
1126 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001127 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001128 }
1129
1130 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1131 conn->send_proxy_ofs = 1;
1132 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001133 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001134 }
1135 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1136 conn->send_proxy_ofs = 1;
1137 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001138 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001139 }
1140
Willy Tarreau51cd5952020-06-05 12:25:38 +02001141 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001142 if (proto && proto->connect) {
1143 int flags = 0;
1144
1145 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1146 flags |= CONNECT_HAS_DATA;
1147 if (!next || next->action != TCPCHK_ACT_EXPECT)
1148 flags |= CONNECT_DELACK_ALWAYS;
1149 status = proto->connect(conn, flags);
1150 }
1151
1152 if (status != SF_ERR_NONE)
1153 goto fail_check;
1154
Christopher Faulet21ddc742020-07-01 15:26:14 +02001155 conn_set_private(conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001156 conn->ctx = cs;
1157
Willy Tarreau51cd5952020-06-05 12:25:38 +02001158#ifdef USE_OPENSSL
1159 if (connect->sni)
1160 ssl_sock_set_servername(conn, connect->sni);
1161 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1162 ssl_sock_set_servername(conn, s->check.sni);
1163
1164 if (connect->alpn)
1165 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1166 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1167 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1168#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001169
1170 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1171 /* Some servers don't like reset on close */
Willy Tarreaub41a6e92021-04-06 17:49:19 +02001172 HA_ATOMIC_AND(&fdtab[cs->conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001173 }
1174
1175 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1176 if (xprt_add_hs(conn) < 0)
1177 status = SF_ERR_RESOURCE;
1178 }
1179
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001180 if (conn_xprt_start(conn) < 0) {
1181 status = SF_ERR_RESOURCE;
1182 goto fail_check;
1183 }
1184
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001185 /* The mux may be initialized now if there isn't server attached to the
1186 * check (email alerts) or if there is a mux proto specified or if there
1187 * is no alpn.
1188 */
1189 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1190 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1191 const struct mux_ops *mux_ops;
1192
Christopher Faulet147b8c92021-04-10 09:00:38 +02001193 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001194 if (connect->mux_proto)
1195 mux_ops = connect->mux_proto->mux;
1196 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1197 mux_ops = check->mux_proto->mux;
1198 else {
1199 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1200 ? PROTO_MODE_HTTP
1201 : PROTO_MODE_TCP);
1202
1203 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1204 }
1205 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001206 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001207 status = SF_ERR_INTERNAL;
1208 goto fail_check;
1209 }
1210 }
1211
Willy Tarreau51cd5952020-06-05 12:25:38 +02001212 fail_check:
1213 /* It can return one of :
1214 * - SF_ERR_NONE if everything's OK
1215 * - SF_ERR_SRVTO if there are no more servers
1216 * - SF_ERR_SRVCL if the connection was refused by the server
1217 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1218 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1219 * - SF_ERR_INTERNAL for any other purely internal errors
1220 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1221 * Note that we try to prevent the network stack from sending the ACK during the
1222 * connect() when a pure TCP check is used (without PROXY protocol).
1223 */
1224 switch (status) {
1225 case SF_ERR_NONE:
1226 /* we allow up to min(inter, timeout.connect) for a connection
1227 * to establish but only when timeout.check is set as it may be
1228 * to short for a full check otherwise
1229 */
1230 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1231
1232 if (proxy->timeout.check && proxy->timeout.connect) {
1233 int t_con = tick_add(now_ms, proxy->timeout.connect);
1234 t->expire = tick_first(t->expire, t_con);
1235 }
1236 break;
1237 case SF_ERR_SRVTO: /* ETIMEDOUT */
1238 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1239 case SF_ERR_PRXCOND:
1240 case SF_ERR_RESOURCE:
1241 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001242 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 +02001243 chk_report_conn_err(check, errno, 0);
1244 ret = TCPCHK_EVAL_STOP;
1245 goto out;
1246 }
1247
1248 /* don't do anything until the connection is established */
1249 if (conn->flags & CO_FL_WAIT_XPRT) {
1250 if (conn->mux) {
1251 if (next && next->action == TCPCHK_ACT_SEND)
1252 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1253 else
1254 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
1255 }
1256 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001257 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001258 goto out;
1259 }
1260
1261 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001262 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001263 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001264 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001265 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001266
1267 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1268 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1269
Christopher Faulet147b8c92021-04-10 09:00:38 +02001270 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001271 return ret;
1272}
1273
1274/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1275 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1276 * TCPCHK_EVAL_STOP if an error occurred.
1277 */
1278enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1279{
1280 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1281 struct tcpcheck_send *send = &rule->send;
1282 struct conn_stream *cs = check->cs;
1283 struct connection *conn = cs_conn(cs);
1284 struct buffer *tmp = NULL;
1285 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001286 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001287
Christopher Faulet147b8c92021-04-10 09:00:38 +02001288 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1289
Christopher Fauletb381a502020-11-25 13:47:00 +01001290 if (check->state & CHK_ST_OUT_ALLOC) {
1291 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001292 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 +01001293 goto out;
1294 }
1295
1296 if (!check_get_buf(check, &check->bo)) {
1297 check->state |= CHK_ST_OUT_ALLOC;
1298 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001299 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 +01001300 goto out;
1301 }
1302
Christopher Faulet39066c22020-11-25 13:34:51 +01001303 /* Data already pending in the output buffer, send them now */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001304 if (b_data(&check->bo)) {
1305 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 +01001306 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001307 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001308
Christopher Fauletb381a502020-11-25 13:47:00 +01001309 /* Always release input buffer when a new send is evaluated */
1310 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001311
1312 switch (send->type) {
1313 case TCPCHK_SEND_STRING:
1314 case TCPCHK_SEND_BINARY:
1315 if (istlen(send->data) >= b_size(&check->bo)) {
1316 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1317 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1318 tcpcheck_get_step_id(check, rule));
1319 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1320 ret = TCPCHK_EVAL_STOP;
1321 goto out;
1322 }
1323 b_putist(&check->bo, send->data);
1324 break;
1325 case TCPCHK_SEND_STRING_LF:
1326 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1327 if (!b_data(&check->bo))
1328 goto out;
1329 break;
1330 case TCPCHK_SEND_BINARY_LF: {
1331 int len = b_size(&check->bo);
1332
1333 tmp = alloc_trash_chunk();
1334 if (!tmp)
1335 goto error_lf;
1336 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1337 if (!b_data(tmp))
1338 goto out;
1339 tmp->area[tmp->data] = '\0';
1340 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1341 goto error_lf;
1342 check->bo.data = len;
1343 break;
1344 }
1345 case TCPCHK_SEND_HTTP: {
1346 struct htx_sl *sl;
1347 struct ist meth, uri, vsn, clen, body;
1348 unsigned int slflags = 0;
1349
1350 tmp = alloc_trash_chunk();
1351 if (!tmp)
1352 goto error_htx;
1353
1354 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1355 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1356 : http_known_methods[send->http.meth.meth]);
1357 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1358 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1359 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1360 }
1361 else
1362 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1363 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1364
1365 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1366 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1367 slflags |= HTX_SL_F_VER_11;
1368 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1369 if (!isttest(send->http.body))
1370 slflags |= HTX_SL_F_BODYLESS;
1371
1372 htx = htx_from_buf(&check->bo);
1373 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1374 if (!sl)
1375 goto error_htx;
1376 sl->info.req.meth = send->http.meth.meth;
1377 if (!http_update_host(htx, sl, uri))
1378 goto error_htx;
1379
1380 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1381 struct tcpcheck_http_hdr *hdr;
1382 struct ist hdr_value;
1383
1384 list_for_each_entry(hdr, &send->http.hdrs, list) {
1385 chunk_reset(tmp);
1386 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1387 if (!b_data(tmp))
1388 continue;
1389 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1390 if (!htx_add_header(htx, hdr->name, hdr_value))
1391 goto error_htx;
1392 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1393 if (!http_update_authority(htx, sl, hdr_value))
1394 goto error_htx;
1395 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001396 if (isteqi(hdr->name, ist("connection")))
1397 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001398 }
1399
1400 }
1401 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1402 chunk_reset(tmp);
1403 httpchk_build_status_header(check->server, tmp);
1404 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1405 goto error_htx;
1406 }
1407
1408
1409 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1410 chunk_reset(tmp);
1411 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1412 body = ist2(b_orig(tmp), b_data(tmp));
1413 }
1414 else
1415 body = send->http.body;
1416 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1417
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001418 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001419 !htx_add_header(htx, ist("Content-length"), clen))
1420 goto error_htx;
1421
1422
1423 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001424 (istlen(body) && !htx_add_data_atonce(htx, body)))
1425 goto error_htx;
1426
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001427 /* no more data are expected */
1428 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001429 htx_to_buf(htx, &check->bo);
1430 break;
1431 }
1432 case TCPCHK_SEND_UNDEF:
1433 /* Should never happen. */
1434 ret = TCPCHK_EVAL_STOP;
1435 goto out;
1436 };
1437
Christopher Faulet39066c22020-11-25 13:34:51 +01001438 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001439 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001440 if (conn->mux->snd_buf(cs, &check->bo,
1441 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1442 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1443 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001444 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 +02001445 goto out;
1446 }
1447 }
1448 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
1449 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1450 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001451 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001452 goto out;
1453 }
1454
1455 out:
1456 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001457 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1458 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001459
1460 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001461 return ret;
1462
1463 error_htx:
1464 if (htx) {
1465 htx_reset(htx);
1466 htx_to_buf(htx, &check->bo);
1467 }
1468 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1469 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001470 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 +02001471 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1472 ret = TCPCHK_EVAL_STOP;
1473 goto out;
1474
1475 error_lf:
1476 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1477 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001478 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 +02001479 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1480 ret = TCPCHK_EVAL_STOP;
1481 goto out;
1482
1483}
1484
1485/* Try to receive data before evaluating a tcp-check expect rule. Returns
1486 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1487 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1488 * TCPCHK_EVAL_STOP if an error occurred.
1489 */
1490enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1491{
1492 struct conn_stream *cs = check->cs;
1493 struct connection *conn = cs_conn(cs);
1494 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1495 size_t max, read, cur_read = 0;
1496 int is_empty;
1497 int read_poll = MAX_READ_POLL_LOOPS;
1498
Christopher Faulet147b8c92021-04-10 09:00:38 +02001499 TRACE_ENTER(CHK_EV_RX_DATA, check);
1500
1501 if (check->wait_list.events & SUB_RETRY_RECV) {
1502 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001503 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001504 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001505
1506 if (cs->flags & CS_FL_EOS)
1507 goto end_recv;
1508
Christopher Faulet147b8c92021-04-10 09:00:38 +02001509 if (check->state & CHK_ST_IN_ALLOC) {
1510 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001511 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001512 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001513
1514 if (!check_get_buf(check, &check->bi)) {
1515 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001516 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001517 goto wait_more_data;
1518 }
1519
Willy Tarreau51cd5952020-06-05 12:25:38 +02001520 /* errors on the connection and the conn-stream were already checked */
1521
1522 /* prepare to detect if the mux needs more room */
1523 cs->flags &= ~CS_FL_WANT_ROOM;
1524
1525 while ((cs->flags & CS_FL_RCV_MORE) ||
1526 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1527 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1528 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1529 cur_read += read;
1530 if (!read ||
1531 (cs->flags & CS_FL_WANT_ROOM) ||
1532 (--read_poll <= 0) ||
1533 (read < max && read >= global.tune.recv_enough))
1534 break;
1535 }
1536
1537 end_recv:
1538 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1539 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1540 /* Report network errors only if we got no other data. Otherwise
1541 * we'll let the upper layers decide whether the response is OK
1542 * or not. It is very common that an RST sent by the server is
1543 * reported as an error just after the last data chunk.
1544 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001545 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001546 goto stop;
1547 }
1548 if (!cur_read) {
1549 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1550 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001551 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001552 goto wait_more_data;
1553 }
1554 if (is_empty) {
1555 int status;
1556
1557 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1558 tcpcheck_get_step_id(check, rule));
1559 if (rule->comment)
1560 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1561
Christopher Faulet147b8c92021-04-10 09:00:38 +02001562 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001563 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1564 set_server_check_status(check, status, trash.area);
1565 goto stop;
1566 }
1567 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001568 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001569
1570 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001571 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1572 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001573
1574 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001575 return ret;
1576
1577 stop:
1578 ret = TCPCHK_EVAL_STOP;
1579 goto out;
1580
1581 wait_more_data:
1582 ret = TCPCHK_EVAL_WAIT;
1583 goto out;
1584}
1585
1586/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1587 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1588 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1589 * error occurred.
1590 */
1591enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1592{
1593 struct htx *htx = htxbuf(&check->bi);
1594 struct htx_sl *sl;
1595 struct htx_blk *blk;
1596 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1597 struct tcpcheck_expect *expect = &rule->expect;
1598 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1599 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1600 struct ist desc = IST_NULL;
1601 int i, match, inverse;
1602
Christopher Faulet147b8c92021-04-10 09:00:38 +02001603 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1604
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001605 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001606
1607 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001608 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001609 status = HCHK_STATUS_L7RSP;
1610 goto error;
1611 }
1612
1613 if (htx_is_empty(htx)) {
1614 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001615 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001616 status = HCHK_STATUS_L7RSP;
1617 goto error;
1618 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001619 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001620 goto wait_more_data;
1621 }
1622
1623 sl = http_get_stline(htx);
1624 check->code = sl->info.res.status;
1625
1626 if (check->server &&
1627 (check->server->proxy->options & PR_O_DISABLE404) &&
1628 (check->server->next_state != SRV_ST_STOPPED) &&
1629 (check->code == 404)) {
1630 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001631 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001632 goto out;
1633 }
1634
1635 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1636 /* Make GCC happy ; initialize match to a failure state. */
1637 match = inverse;
1638 status = expect->err_status;
1639
1640 switch (expect->type) {
1641 case TCPCHK_EXPECT_HTTP_STATUS:
1642 match = 0;
1643 for (i = 0; i < expect->codes.num; i++) {
1644 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1645 sl->info.res.status <= expect->codes.codes[i][1]) {
1646 match = 1;
1647 break;
1648 }
1649 }
1650
1651 /* Set status and description in case of error */
1652 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1653 if (LIST_ISEMPTY(&expect->onerror_fmt))
1654 desc = htx_sl_res_reason(sl);
1655 break;
1656 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1657 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1658
1659 /* Set status and description in case of error */
1660 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1661 if (LIST_ISEMPTY(&expect->onerror_fmt))
1662 desc = htx_sl_res_reason(sl);
1663 break;
1664
1665 case TCPCHK_EXPECT_HTTP_HEADER: {
1666 struct http_hdr_ctx ctx;
1667 struct ist npat, vpat, value;
1668 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1669
1670 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1671 nbuf = alloc_trash_chunk();
1672 if (!nbuf) {
1673 status = HCHK_STATUS_L7RSP;
1674 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001675 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001676 goto error;
1677 }
1678 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1679 if (!b_data(nbuf)) {
1680 status = HCHK_STATUS_L7RSP;
1681 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001682 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001683 goto error;
1684 }
1685 npat = ist2(b_orig(nbuf), b_data(nbuf));
1686 }
1687 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1688 npat = expect->hdr.name;
1689
1690 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1691 vbuf = alloc_trash_chunk();
1692 if (!vbuf) {
1693 status = HCHK_STATUS_L7RSP;
1694 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001695 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001696 goto error;
1697 }
1698 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1699 if (!b_data(vbuf)) {
1700 status = HCHK_STATUS_L7RSP;
1701 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001702 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001703 goto error;
1704 }
1705 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1706 }
1707 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1708 vpat = expect->hdr.value;
1709
1710 match = 0;
1711 ctx.blk = NULL;
1712 while (1) {
1713 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1714 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1715 if (!http_find_str_header(htx, npat, &ctx, full))
1716 goto end_of_match;
1717 break;
1718 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1719 if (!http_find_pfx_header(htx, npat, &ctx, full))
1720 goto end_of_match;
1721 break;
1722 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1723 if (!http_find_sfx_header(htx, npat, &ctx, full))
1724 goto end_of_match;
1725 break;
1726 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1727 if (!http_find_sub_header(htx, npat, &ctx, full))
1728 goto end_of_match;
1729 break;
1730 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1731 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1732 goto end_of_match;
1733 break;
1734 default:
1735 /* should never happen */
1736 goto end_of_match;
1737 }
1738
1739 /* A header has matched the name pattern, let's test its
1740 * value now (always defined from there). If there is no
1741 * value pattern, it is a good match.
1742 */
1743
1744 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1745 match = 1;
1746 goto end_of_match;
1747 }
1748
1749 value = ctx.value;
1750 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1751 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1752 if (isteq(value, vpat)) {
1753 match = 1;
1754 goto end_of_match;
1755 }
1756 break;
1757 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1758 if (istlen(value) < istlen(vpat))
1759 break;
1760 value = ist2(istptr(value), istlen(vpat));
1761 if (isteq(value, vpat)) {
1762 match = 1;
1763 goto end_of_match;
1764 }
1765 break;
1766 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1767 if (istlen(value) < istlen(vpat))
1768 break;
1769 value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
1770 if (isteq(value, vpat)) {
1771 match = 1;
1772 goto end_of_match;
1773 }
1774 break;
1775 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1776 if (isttest(istist(value, vpat))) {
1777 match = 1;
1778 goto end_of_match;
1779 }
1780 break;
1781 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1782 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1783 match = 1;
1784 goto end_of_match;
1785 }
1786 break;
1787 }
1788 }
1789
1790 end_of_match:
1791 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1792 if (LIST_ISEMPTY(&expect->onerror_fmt))
1793 desc = htx_sl_res_reason(sl);
1794 break;
1795 }
1796
1797 case TCPCHK_EXPECT_HTTP_BODY:
1798 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1799 case TCPCHK_EXPECT_HTTP_BODY_LF:
1800 match = 0;
1801 chunk_reset(&trash);
1802 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1803 enum htx_blk_type type = htx_get_blk_type(blk);
1804
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001805 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001806 break;
1807 if (type == HTX_BLK_DATA) {
1808 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1809 break;
1810 }
1811 }
1812
1813 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001814 if (!last_read) {
1815 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001816 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001817 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001818 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1819 if (LIST_ISEMPTY(&expect->onerror_fmt))
1820 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001821 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001822 goto error;
1823 }
1824
1825 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1826 tmp = alloc_trash_chunk();
1827 if (!tmp) {
1828 status = HCHK_STATUS_L7RSP;
1829 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001830 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001831 goto error;
1832 }
1833 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1834 if (!b_data(tmp)) {
1835 status = HCHK_STATUS_L7RSP;
1836 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001837 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001838 goto error;
1839 }
1840 }
1841
1842 if (!last_read &&
1843 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1844 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1845 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1846 ret = TCPCHK_EVAL_WAIT;
1847 goto out;
1848 }
1849
1850 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1851 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1852 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1853 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1854 else
1855 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1856
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001857 /* Wait for more data on mismatch only if no minimum is defined (-1),
1858 * otherwise the absence of match is already conclusive.
1859 */
1860 if (!match && !last_read && (expect->min_recv == -1)) {
1861 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001862 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001863 goto out;
1864 }
1865
Willy Tarreau51cd5952020-06-05 12:25:38 +02001866 /* Set status and description in case of error */
1867 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1868 if (LIST_ISEMPTY(&expect->onerror_fmt))
1869 desc = (inverse
1870 ? ist("HTTP check matched unwanted content")
1871 : ist("HTTP content check did not match"));
1872 break;
1873
1874
1875 default:
1876 /* should never happen */
1877 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1878 goto error;
1879 }
1880
Christopher Faulet147b8c92021-04-10 09:00:38 +02001881 if (!(match ^ inverse)) {
1882 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001883 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001884 }
1885
1886 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001887
1888 out:
1889 free_trash_chunk(tmp);
1890 free_trash_chunk(nbuf);
1891 free_trash_chunk(vbuf);
1892 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001893 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001894 return ret;
1895
1896 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001897 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001898 ret = TCPCHK_EVAL_STOP;
1899 msg = alloc_trash_chunk();
1900 if (msg)
1901 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1902 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1903 goto out;
1904
1905 wait_more_data:
1906 ret = TCPCHK_EVAL_WAIT;
1907 goto out;
1908}
1909
1910/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1911 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1912 * if an error occurred.
1913 */
1914enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1915{
1916 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1917 struct tcpcheck_expect *expect = &rule->expect;
1918 struct buffer *msg = NULL, *tmp = NULL;
1919 struct ist desc = IST_NULL;
1920 enum healthcheck_status status;
1921 int match, inverse;
1922
Christopher Faulet147b8c92021-04-10 09:00:38 +02001923 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1924
Willy Tarreau51cd5952020-06-05 12:25:38 +02001925 last_read |= b_full(&check->bi);
1926
1927 /* The current expect might need more data than the previous one, check again
1928 * that the minimum amount data required to match is respected.
1929 */
1930 if (!last_read) {
1931 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1932 (b_data(&check->bi) < istlen(expect->data))) {
1933 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001934 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001935 goto out;
1936 }
1937 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1938 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001939 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001940 goto out;
1941 }
1942 }
1943
1944 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1945 /* Make GCC happy ; initialize match to a failure state. */
1946 match = inverse;
1947 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1948
1949 switch (expect->type) {
1950 case TCPCHK_EXPECT_STRING:
1951 case TCPCHK_EXPECT_BINARY:
1952 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
1953 break;
1954 case TCPCHK_EXPECT_STRING_REGEX:
1955 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
1956 break;
1957
1958 case TCPCHK_EXPECT_BINARY_REGEX:
1959 chunk_reset(&trash);
1960 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
1961 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
1962 break;
1963
1964 case TCPCHK_EXPECT_STRING_LF:
1965 case TCPCHK_EXPECT_BINARY_LF:
1966 match = 0;
1967 tmp = alloc_trash_chunk();
1968 if (!tmp) {
1969 status = HCHK_STATUS_L7RSP;
1970 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001971 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001972 goto error;
1973 }
1974 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1975 if (!b_data(tmp)) {
1976 status = HCHK_STATUS_L7RSP;
1977 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001978 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001979 goto error;
1980 }
1981 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
1982 int len = tmp->data;
1983 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
1984 status = HCHK_STATUS_L7RSP;
1985 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001986 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001987 goto error;
1988 }
1989 tmp->data = len;
1990 }
1991 if (b_data(&check->bi) < tmp->data) {
1992 if (!last_read) {
1993 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001994 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001995 goto out;
1996 }
1997 break;
1998 }
1999 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2000 break;
2001
2002 case TCPCHK_EXPECT_CUSTOM:
2003 if (expect->custom)
2004 ret = expect->custom(check, rule, last_read);
2005 goto out;
2006 default:
2007 /* Should never happen. */
2008 ret = TCPCHK_EVAL_STOP;
2009 goto out;
2010 }
2011
2012
2013 /* Wait for more data on mismatch only if no minimum is defined (-1),
2014 * otherwise the absence of match is already conclusive.
2015 */
2016 if (!match && !last_read && (expect->min_recv == -1)) {
2017 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002018 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002019 goto out;
2020 }
2021
2022 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002023 if (match ^ inverse) {
2024 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002025 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002026 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002027
2028 error:
2029 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002030 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002031 ret = TCPCHK_EVAL_STOP;
2032 msg = alloc_trash_chunk();
2033 if (msg)
2034 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2035 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2036 free_trash_chunk(msg);
2037
2038 out:
2039 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002040 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002041 return ret;
2042}
2043
2044/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2045 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2046 * waits.
2047 */
2048enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2049{
2050 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2051 struct act_rule *act_rule;
2052 enum act_return act_ret;
2053
2054 act_rule =rule->action_kw.rule;
2055 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2056 if (act_ret != ACT_RET_CONT) {
2057 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2058 tcpcheck_get_step_id(check, rule));
2059 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2060 ret = TCPCHK_EVAL_STOP;
2061 }
2062
2063 return ret;
2064}
2065
2066/* Executes a tcp-check ruleset. Note that this is called both from the
2067 * connection's wake() callback and from the check scheduling task. It returns
2068 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2069 * presenting the risk of an fd replacement.
2070 *
2071 * Please do NOT place any return statement in this function and only leave
2072 * via the out_end_tcpcheck label after setting retcode.
2073 */
2074int tcpcheck_main(struct check *check)
2075{
2076 struct tcpcheck_rule *rule;
2077 struct conn_stream *cs = check->cs;
2078 struct connection *conn = cs_conn(cs);
2079 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002080 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002081 enum tcpcheck_eval_ret eval_ret;
2082
2083 /* here, we know that the check is complete or that it failed */
2084 if (check->result != CHK_RES_UNKNOWN)
2085 goto out;
2086
Christopher Faulet147b8c92021-04-10 09:00:38 +02002087 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2088
Willy Tarreau51cd5952020-06-05 12:25:38 +02002089 /* Note: the conn-stream and the connection may only be undefined before
2090 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002091 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002092 */
2093
2094 /* 1- check for connection error, if any */
2095 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2096 goto out_end_tcpcheck;
2097
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002098 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002099 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002100 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002101 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002102 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2103 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002104
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002105 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002106 * tcp-check variables */
2107 else {
2108 struct tcpcheck_var *var;
2109
2110 /* First evaluation, create a session */
2111 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2112 if (!check->sess) {
2113 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002114 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002115 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2116 goto out_end_tcpcheck;
2117 }
2118 vars_init(&check->vars, SCOPE_CHECK);
2119 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2120
2121 /* Preset tcp-check variables */
2122 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2123 struct sample smp;
2124
2125 memset(&smp, 0, sizeof(smp));
2126 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2127 smp.data = var->data;
2128 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2129 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002130 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002131 }
2132
2133 /* Now evaluate the tcp-check rules */
2134
2135 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2136 check->code = 0;
2137 switch (rule->action) {
2138 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002139 /* Not the first connection, release it first */
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002140 if (cs && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002141 check->state |= CHK_ST_CLOSE_CONN;
2142 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002143 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002144
2145 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002146
2147 /* We are still waiting the connection gets closed */
2148 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002149 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002150 eval_ret = TCPCHK_EVAL_WAIT;
2151 break;
2152 }
2153
Christopher Faulet147b8c92021-04-10 09:00:38 +02002154 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002155 eval_ret = tcpcheck_eval_connect(check, rule);
2156
2157 /* Refresh conn-stream and connection */
2158 cs = check->cs;
2159 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002160 last_read = 0;
2161 must_read = ((cs && IS_HTX_CS(cs)) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002162 break;
2163 case TCPCHK_ACT_SEND:
2164 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002165 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002166 eval_ret = tcpcheck_eval_send(check, rule);
2167 must_read = 1;
2168 break;
2169 case TCPCHK_ACT_EXPECT:
2170 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002171 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002172 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002173 eval_ret = tcpcheck_eval_recv(check, rule);
2174 if (eval_ret == TCPCHK_EVAL_STOP)
2175 goto out_end_tcpcheck;
2176 else if (eval_ret == TCPCHK_EVAL_WAIT)
2177 goto out;
2178 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2179 must_read = 0;
2180 }
2181
2182 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2183 ? tcpcheck_eval_expect_http(check, rule, last_read)
2184 : tcpcheck_eval_expect(check, rule, last_read));
2185
2186 if (eval_ret == TCPCHK_EVAL_WAIT) {
2187 check->current_step = rule->expect.head;
2188 if (!(check->wait_list.events & SUB_RETRY_RECV))
2189 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2190 }
2191 break;
2192 case TCPCHK_ACT_ACTION_KW:
2193 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002194 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002195 eval_ret = tcpcheck_eval_action_kw(check, rule);
2196 break;
2197 default:
2198 /* Otherwise, just go to the next one and don't update
2199 * the current step
2200 */
2201 eval_ret = TCPCHK_EVAL_CONTINUE;
2202 break;
2203 }
2204
2205 switch (eval_ret) {
2206 case TCPCHK_EVAL_CONTINUE:
2207 break;
2208 case TCPCHK_EVAL_WAIT:
2209 goto out;
2210 case TCPCHK_EVAL_STOP:
2211 goto out_end_tcpcheck;
2212 }
2213 }
2214
2215 /* All rules was evaluated */
2216 if (check->current_step) {
2217 rule = check->current_step;
2218
Christopher Faulet147b8c92021-04-10 09:00:38 +02002219 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2220
Willy Tarreau51cd5952020-06-05 12:25:38 +02002221 if (rule->action == TCPCHK_ACT_EXPECT) {
2222 struct buffer *msg;
2223 enum healthcheck_status status;
2224
2225 if (check->server &&
2226 (check->server->proxy->options & PR_O_DISABLE404) &&
2227 (check->server->next_state != SRV_ST_STOPPED) &&
2228 (check->code == 404)) {
2229 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002230 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002231 goto out_end_tcpcheck;
2232 }
2233
2234 msg = alloc_trash_chunk();
2235 if (msg)
2236 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2237 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2238 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2239 free_trash_chunk(msg);
2240 }
2241 else if (rule->action == TCPCHK_ACT_CONNECT) {
2242 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2243 enum healthcheck_status status = HCHK_STATUS_L4OK;
2244#ifdef USE_OPENSSL
2245 if (ssl_sock_is_ssl(conn))
2246 status = HCHK_STATUS_L6OK;
2247#endif
2248 set_server_check_status(check, status, msg);
2249 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002250 else
2251 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002252 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002253 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002254 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002255 }
2256 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002257
2258 out_end_tcpcheck:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002259 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) {
2260 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002261 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002262 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002263
Christopher Fauletb381a502020-11-25 13:47:00 +01002264 /* the tcpcheck is finished, release in/out buffer now */
2265 check_release_buf(check, &check->bi);
2266 check_release_buf(check, &check->bo);
2267
Willy Tarreau51cd5952020-06-05 12:25:38 +02002268 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002269 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002270 return retcode;
2271}
2272
2273
2274/**************************************************************************/
2275/******************* Internals to parse tcp-check rules *******************/
2276/**************************************************************************/
2277struct action_kw_list tcp_check_keywords = {
2278 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2279};
2280
2281/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2282 * returned on error.
2283 */
2284struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2285 struct list *rules, struct action_kw *kw,
2286 const char *file, int line, char **errmsg)
2287{
2288 struct tcpcheck_rule *chk = NULL;
2289 struct act_rule *actrule = NULL;
2290
2291 actrule = calloc(1, sizeof(*actrule));
2292 if (!actrule) {
2293 memprintf(errmsg, "out of memory");
2294 goto error;
2295 }
2296 actrule->kw = kw;
2297 actrule->from = ACT_F_TCP_CHK;
2298
2299 cur_arg++;
2300 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2301 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2302 goto error;
2303 }
2304
2305 chk = calloc(1, sizeof(*chk));
2306 if (!chk) {
2307 memprintf(errmsg, "out of memory");
2308 goto error;
2309 }
2310 chk->action = TCPCHK_ACT_ACTION_KW;
2311 chk->action_kw.rule = actrule;
2312 return chk;
2313
2314 error:
2315 free(actrule);
2316 return NULL;
2317}
2318
2319/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2320 * returned on error.
2321 */
2322struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2323 const char *file, int line, char **errmsg)
2324{
2325 struct tcpcheck_rule *chk = NULL;
2326 struct sockaddr_storage *sk = NULL;
2327 char *comment = NULL, *sni = NULL, *alpn = NULL;
2328 struct sample_expr *port_expr = NULL;
2329 const struct mux_proto_list *mux_proto = NULL;
2330 unsigned short conn_opts = 0;
2331 long port = 0;
2332 int alpn_len = 0;
2333
2334 list_for_each_entry(chk, rules, list) {
2335 if (chk->action == TCPCHK_ACT_CONNECT)
2336 break;
2337 if (chk->action == TCPCHK_ACT_COMMENT ||
2338 chk->action == TCPCHK_ACT_ACTION_KW ||
2339 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2340 continue;
2341
2342 memprintf(errmsg, "first step MUST also be a 'connect', "
2343 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2344 "when there is a 'connect' step in the tcp-check ruleset");
2345 goto error;
2346 }
2347
2348 cur_arg++;
2349 while (*(args[cur_arg])) {
2350 if (strcmp(args[cur_arg], "default") == 0)
2351 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2352 else if (strcmp(args[cur_arg], "addr") == 0) {
2353 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002354
2355 if (!*(args[cur_arg+1])) {
2356 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2357 goto error;
2358 }
2359
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002360 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2361 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002362 if (!sk) {
2363 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2364 goto error;
2365 }
2366
Willy Tarreau51cd5952020-06-05 12:25:38 +02002367 cur_arg++;
2368 }
2369 else if (strcmp(args[cur_arg], "port") == 0) {
2370 const char *p, *end;
2371
2372 if (!*(args[cur_arg+1])) {
2373 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2374 goto error;
2375 }
2376 cur_arg++;
2377
2378 port = 0;
2379 release_sample_expr(port_expr);
2380 p = args[cur_arg]; end = p + strlen(p);
2381 port = read_uint(&p, end);
2382 if (p != end) {
2383 int idx = 0;
2384
2385 px->conf.args.ctx = ARGC_SRV;
2386 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
2387 file, line, errmsg, &px->conf.args, NULL);
2388
2389 if (!port_expr) {
2390 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2391 goto error;
2392 }
2393 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2394 memprintf(errmsg, "error detected while parsing port expression : "
2395 " fetch method '%s' extracts information from '%s', "
2396 "none of which is available here.\n",
2397 args[cur_arg], sample_src_names(port_expr->fetch->use));
2398 goto error;
2399 }
2400 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2401 }
2402 else if (port > 65535 || port < 1) {
2403 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2404 args[cur_arg]);
2405 goto error;
2406 }
2407 }
2408 else if (strcmp(args[cur_arg], "proto") == 0) {
2409 if (!*(args[cur_arg+1])) {
2410 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2411 goto error;
2412 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002413 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002414 if (!mux_proto) {
2415 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2416 goto error;
2417 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002418
2419 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2420 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2421 goto error;
2422 }
2423 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2424 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2425 goto error;
2426 }
2427
Willy Tarreau51cd5952020-06-05 12:25:38 +02002428 cur_arg++;
2429 }
2430 else if (strcmp(args[cur_arg], "comment") == 0) {
2431 if (!*(args[cur_arg+1])) {
2432 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2433 goto error;
2434 }
2435 cur_arg++;
2436 free(comment);
2437 comment = strdup(args[cur_arg]);
2438 if (!comment) {
2439 memprintf(errmsg, "out of memory");
2440 goto error;
2441 }
2442 }
2443 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2444 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2445 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2446 conn_opts |= TCPCHK_OPT_SOCKS4;
2447 else if (strcmp(args[cur_arg], "linger") == 0)
2448 conn_opts |= TCPCHK_OPT_LINGER;
2449#ifdef USE_OPENSSL
2450 else if (strcmp(args[cur_arg], "ssl") == 0) {
2451 px->options |= PR_O_TCPCHK_SSL;
2452 conn_opts |= TCPCHK_OPT_SSL;
2453 }
2454 else if (strcmp(args[cur_arg], "sni") == 0) {
2455 if (!*(args[cur_arg+1])) {
2456 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2457 goto error;
2458 }
2459 cur_arg++;
2460 free(sni);
2461 sni = strdup(args[cur_arg]);
2462 if (!sni) {
2463 memprintf(errmsg, "out of memory");
2464 goto error;
2465 }
2466 }
2467 else if (strcmp(args[cur_arg], "alpn") == 0) {
2468#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2469 free(alpn);
2470 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2471 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2472 goto error;
2473 }
2474 cur_arg++;
2475#else
2476 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2477 goto error;
2478#endif
2479 }
2480#endif /* USE_OPENSSL */
2481
2482 else {
2483 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2484#ifdef USE_OPENSSL
2485 ", 'ssl', 'sni', 'alpn'"
2486#endif /* USE_OPENSSL */
2487 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2488 args[cur_arg]);
2489 goto error;
2490 }
2491 cur_arg++;
2492 }
2493
2494 chk = calloc(1, sizeof(*chk));
2495 if (!chk) {
2496 memprintf(errmsg, "out of memory");
2497 goto error;
2498 }
2499 chk->action = TCPCHK_ACT_CONNECT;
2500 chk->comment = comment;
2501 chk->connect.port = port;
2502 chk->connect.options = conn_opts;
2503 chk->connect.sni = sni;
2504 chk->connect.alpn = alpn;
2505 chk->connect.alpn_len= alpn_len;
2506 chk->connect.port_expr= port_expr;
2507 chk->connect.mux_proto= mux_proto;
2508 if (sk)
2509 chk->connect.addr = *sk;
2510 return chk;
2511
2512 error:
2513 free(alpn);
2514 free(sni);
2515 free(comment);
2516 release_sample_expr(port_expr);
2517 return NULL;
2518}
2519
2520/* Parses and creates a tcp-check send rule. NULL is returned on error */
2521struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2522 const char *file, int line, char **errmsg)
2523{
2524 struct tcpcheck_rule *chk = NULL;
2525 char *comment = NULL, *data = NULL;
2526 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2527
2528 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2529 type = TCPCHK_SEND_BINARY_LF;
2530 else if (strcmp(args[cur_arg], "send-binary") == 0)
2531 type = TCPCHK_SEND_BINARY;
2532 else if (strcmp(args[cur_arg], "send-lf") == 0)
2533 type = TCPCHK_SEND_STRING_LF;
2534 else if (strcmp(args[cur_arg], "send") == 0)
2535 type = TCPCHK_SEND_STRING;
2536
2537 if (!*(args[cur_arg+1])) {
2538 memprintf(errmsg, "'%s' expects a %s as argument",
2539 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2540 goto error;
2541 }
2542
2543 data = args[cur_arg+1];
2544
2545 cur_arg += 2;
2546 while (*(args[cur_arg])) {
2547 if (strcmp(args[cur_arg], "comment") == 0) {
2548 if (!*(args[cur_arg+1])) {
2549 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2550 goto error;
2551 }
2552 cur_arg++;
2553 free(comment);
2554 comment = strdup(args[cur_arg]);
2555 if (!comment) {
2556 memprintf(errmsg, "out of memory");
2557 goto error;
2558 }
2559 }
2560 else {
2561 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2562 args[cur_arg]);
2563 goto error;
2564 }
2565 cur_arg++;
2566 }
2567
2568 chk = calloc(1, sizeof(*chk));
2569 if (!chk) {
2570 memprintf(errmsg, "out of memory");
2571 goto error;
2572 }
2573 chk->action = TCPCHK_ACT_SEND;
2574 chk->comment = comment;
2575 chk->send.type = type;
2576
2577 switch (chk->send.type) {
2578 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002579 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002580 if (!isttest(chk->send.data)) {
2581 memprintf(errmsg, "out of memory");
2582 goto error;
2583 }
2584 break;
2585 case TCPCHK_SEND_BINARY: {
2586 int len = chk->send.data.len;
2587 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2588 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2589 goto error;
2590 }
2591 chk->send.data.len = len;
2592 break;
2593 }
2594 case TCPCHK_SEND_STRING_LF:
2595 case TCPCHK_SEND_BINARY_LF:
2596 LIST_INIT(&chk->send.fmt);
2597 px->conf.args.ctx = ARGC_SRV;
2598 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2599 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2600 goto error;
2601 }
2602 break;
2603 case TCPCHK_SEND_HTTP:
2604 case TCPCHK_SEND_UNDEF:
2605 goto error;
2606 }
2607
2608 return chk;
2609
2610 error:
2611 free(chk);
2612 free(comment);
2613 return NULL;
2614}
2615
2616/* Parses and creates a http-check send rule. NULL is returned on error */
2617struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2618 const char *file, int line, char **errmsg)
2619{
2620 struct tcpcheck_rule *chk = NULL;
2621 struct tcpcheck_http_hdr *hdr = NULL;
2622 struct http_hdr hdrs[global.tune.max_http_hdr];
2623 char *meth = NULL, *uri = NULL, *vsn = NULL;
2624 char *body = NULL, *comment = NULL;
2625 unsigned int flags = 0;
2626 int i = 0, host_hdr = -1;
2627
2628 cur_arg++;
2629 while (*(args[cur_arg])) {
2630 if (strcmp(args[cur_arg], "meth") == 0) {
2631 if (!*(args[cur_arg+1])) {
2632 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2633 goto error;
2634 }
2635 cur_arg++;
2636 meth = args[cur_arg];
2637 }
2638 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2639 if (!*(args[cur_arg+1])) {
2640 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2641 goto error;
2642 }
2643 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2644 if (strcmp(args[cur_arg], "uri-lf") == 0)
2645 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2646 cur_arg++;
2647 uri = args[cur_arg];
2648 }
2649 else if (strcmp(args[cur_arg], "ver") == 0) {
2650 if (!*(args[cur_arg+1])) {
2651 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2652 goto error;
2653 }
2654 cur_arg++;
2655 vsn = args[cur_arg];
2656 }
2657 else if (strcmp(args[cur_arg], "hdr") == 0) {
2658 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2659 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2660 goto error;
2661 }
2662
2663 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2664 if (host_hdr >= 0) {
2665 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2666 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2667 goto error;
2668 }
2669 host_hdr = i;
2670 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002671 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002672 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2673 goto skip_hdr;
2674
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002675 hdrs[i].n = ist(args[cur_arg + 1]);
2676 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002677 i++;
2678 skip_hdr:
2679 cur_arg += 2;
2680 }
2681 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2682 if (!*(args[cur_arg+1])) {
2683 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2684 goto error;
2685 }
2686 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2687 if (strcmp(args[cur_arg], "body-lf") == 0)
2688 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2689 cur_arg++;
2690 body = args[cur_arg];
2691 }
2692 else if (strcmp(args[cur_arg], "comment") == 0) {
2693 if (!*(args[cur_arg+1])) {
2694 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2695 goto error;
2696 }
2697 cur_arg++;
2698 free(comment);
2699 comment = strdup(args[cur_arg]);
2700 if (!comment) {
2701 memprintf(errmsg, "out of memory");
2702 goto error;
2703 }
2704 }
2705 else {
2706 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2707 " but got '%s' as argument.", args[cur_arg]);
2708 goto error;
2709 }
2710 cur_arg++;
2711 }
2712
2713 hdrs[i].n = hdrs[i].v = IST_NULL;
2714
2715 chk = calloc(1, sizeof(*chk));
2716 if (!chk) {
2717 memprintf(errmsg, "out of memory");
2718 goto error;
2719 }
2720 chk->action = TCPCHK_ACT_SEND;
2721 chk->comment = comment; comment = NULL;
2722 chk->send.type = TCPCHK_SEND_HTTP;
2723 chk->send.http.flags = flags;
2724 LIST_INIT(&chk->send.http.hdrs);
2725
2726 if (meth) {
2727 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2728 chk->send.http.meth.str.area = strdup(meth);
2729 chk->send.http.meth.str.data = strlen(meth);
2730 if (!chk->send.http.meth.str.area) {
2731 memprintf(errmsg, "out of memory");
2732 goto error;
2733 }
2734 }
2735 if (uri) {
2736 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2737 LIST_INIT(&chk->send.http.uri_fmt);
2738 px->conf.args.ctx = ARGC_SRV;
2739 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2740 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2741 goto error;
2742 }
2743 }
2744 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002745 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002746 if (!isttest(chk->send.http.uri)) {
2747 memprintf(errmsg, "out of memory");
2748 goto error;
2749 }
2750 }
2751 }
2752 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002753 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002754 if (!isttest(chk->send.http.vsn)) {
2755 memprintf(errmsg, "out of memory");
2756 goto error;
2757 }
2758 }
2759 for (i = 0; istlen(hdrs[i].n); i++) {
2760 hdr = calloc(1, sizeof(*hdr));
2761 if (!hdr) {
2762 memprintf(errmsg, "out of memory");
2763 goto error;
2764 }
2765 LIST_INIT(&hdr->value);
2766 hdr->name = istdup(hdrs[i].n);
2767 if (!isttest(hdr->name)) {
2768 memprintf(errmsg, "out of memory");
2769 goto error;
2770 }
2771
2772 ist0(hdrs[i].v);
2773 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2774 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002775 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002776 hdr = NULL;
2777 }
2778
2779 if (body) {
2780 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2781 LIST_INIT(&chk->send.http.body_fmt);
2782 px->conf.args.ctx = ARGC_SRV;
2783 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2784 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2785 goto error;
2786 }
2787 }
2788 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002789 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002790 if (!isttest(chk->send.http.body)) {
2791 memprintf(errmsg, "out of memory");
2792 goto error;
2793 }
2794 }
2795 }
2796
2797 return chk;
2798
2799 error:
2800 free_tcpcheck_http_hdr(hdr);
2801 free_tcpcheck(chk, 0);
2802 free(comment);
2803 return NULL;
2804}
2805
2806/* Parses and creates a http-check comment rule. NULL is returned on error */
2807struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2808 const char *file, int line, char **errmsg)
2809{
2810 struct tcpcheck_rule *chk = NULL;
2811 char *comment = NULL;
2812
2813 if (!*(args[cur_arg+1])) {
2814 memprintf(errmsg, "expects a string as argument");
2815 goto error;
2816 }
2817 cur_arg++;
2818 comment = strdup(args[cur_arg]);
2819 if (!comment) {
2820 memprintf(errmsg, "out of memory");
2821 goto error;
2822 }
2823
2824 chk = calloc(1, sizeof(*chk));
2825 if (!chk) {
2826 memprintf(errmsg, "out of memory");
2827 goto error;
2828 }
2829 chk->action = TCPCHK_ACT_COMMENT;
2830 chk->comment = comment;
2831 return chk;
2832
2833 error:
2834 free(comment);
2835 return NULL;
2836}
2837
2838/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2839 * on error. <proto> is set to the right protocol flags (covered by the
2840 * TCPCHK_RULES_PROTO_CHK mask).
2841 */
2842struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2843 struct list *rules, unsigned int proto,
2844 const char *file, int line, char **errmsg)
2845{
2846 struct tcpcheck_rule *prev_check, *chk = NULL;
2847 struct sample_expr *status_expr = NULL;
2848 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2849 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2850 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2851 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2852 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2853 unsigned int flags = 0;
2854 long min_recv = -1;
2855 int inverse = 0;
2856
2857 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2858 if (!*(args[cur_arg+1])) {
2859 memprintf(errmsg, "expects at least a matching pattern as arguments");
2860 goto error;
2861 }
2862
2863 cur_arg++;
2864 while (*(args[cur_arg])) {
2865 int in_pattern = 0;
2866
2867 rescan:
2868 if (strcmp(args[cur_arg], "min-recv") == 0) {
2869 if (in_pattern) {
2870 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2871 goto error;
2872 }
2873 if (!*(args[cur_arg+1])) {
2874 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2875 goto error;
2876 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002877 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002878 cur_arg++;
2879 min_recv = atol(args[cur_arg]);
2880 if (min_recv < -1 || min_recv > INT_MAX) {
2881 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2882 goto error;
2883 }
2884 }
2885 else if (*(args[cur_arg]) == '!') {
2886 in_pattern = 1;
2887 while (*(args[cur_arg]) == '!') {
2888 inverse = !inverse;
2889 args[cur_arg]++;
2890 }
2891 if (!*(args[cur_arg]))
2892 cur_arg++;
2893 goto rescan;
2894 }
2895 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2896 if (type != TCPCHK_EXPECT_UNDEF) {
2897 memprintf(errmsg, "only on pattern expected");
2898 goto error;
2899 }
2900 if (proto != TCPCHK_RULES_HTTP_CHK)
2901 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2902 else
2903 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2904
2905 if (!*(args[cur_arg+1])) {
2906 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2907 goto error;
2908 }
2909 cur_arg++;
2910 pattern = args[cur_arg];
2911 }
2912 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2913 if (proto == TCPCHK_RULES_HTTP_CHK)
2914 goto bad_http_kw;
2915 if (type != TCPCHK_EXPECT_UNDEF) {
2916 memprintf(errmsg, "only on pattern expected");
2917 goto error;
2918 }
2919 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2920
2921 if (!*(args[cur_arg+1])) {
2922 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2923 goto error;
2924 }
2925 cur_arg++;
2926 pattern = args[cur_arg];
2927 }
2928 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2929 if (type != TCPCHK_EXPECT_UNDEF) {
2930 memprintf(errmsg, "only on pattern expected");
2931 goto error;
2932 }
2933 if (proto != TCPCHK_RULES_HTTP_CHK)
2934 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2935 else {
2936 if (*(args[cur_arg]) != 's')
2937 goto bad_http_kw;
2938 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2939 }
2940
2941 if (!*(args[cur_arg+1])) {
2942 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2943 goto error;
2944 }
2945 cur_arg++;
2946 pattern = args[cur_arg];
2947 }
2948 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
2949 if (proto != TCPCHK_RULES_HTTP_CHK)
2950 goto bad_tcp_kw;
2951 if (type != TCPCHK_EXPECT_UNDEF) {
2952 memprintf(errmsg, "only on pattern expected");
2953 goto error;
2954 }
2955 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
2956
2957 if (!*(args[cur_arg+1])) {
2958 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2959 goto error;
2960 }
2961 cur_arg++;
2962 pattern = args[cur_arg];
2963 }
2964 else if (strcmp(args[cur_arg], "custom") == 0) {
2965 if (in_pattern) {
2966 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2967 goto error;
2968 }
2969 if (type != TCPCHK_EXPECT_UNDEF) {
2970 memprintf(errmsg, "only on pattern expected");
2971 goto error;
2972 }
2973 type = TCPCHK_EXPECT_CUSTOM;
2974 }
2975 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
2976 int orig_arg = cur_arg;
2977
2978 if (proto != TCPCHK_RULES_HTTP_CHK)
2979 goto bad_tcp_kw;
2980 if (type != TCPCHK_EXPECT_UNDEF) {
2981 memprintf(errmsg, "only on pattern expected");
2982 goto error;
2983 }
2984 type = TCPCHK_EXPECT_HTTP_HEADER;
2985
2986 if (strcmp(args[cur_arg], "fhdr") == 0)
2987 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
2988
2989 /* Parse the name pattern, mandatory */
2990 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
2991 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
2992 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
2993 args[orig_arg]);
2994 goto error;
2995 }
2996
2997 if (strcmp(args[cur_arg+1], "name-lf") == 0)
2998 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
2999
3000 cur_arg += 2;
3001 if (strcmp(args[cur_arg], "-m") == 0) {
3002 if (!*(args[cur_arg+1])) {
3003 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3004 args[orig_arg], args[cur_arg]);
3005 goto error;
3006 }
3007 if (strcmp(args[cur_arg+1], "str") == 0)
3008 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3009 else if (strcmp(args[cur_arg+1], "beg") == 0)
3010 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3011 else if (strcmp(args[cur_arg+1], "end") == 0)
3012 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3013 else if (strcmp(args[cur_arg+1], "sub") == 0)
3014 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3015 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3016 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3017 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3018 args[orig_arg]);
3019 goto error;
3020 }
3021 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3022 }
3023 else {
3024 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3025 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3026 goto error;
3027 }
3028 cur_arg += 2;
3029 }
3030 else
3031 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3032 npat = args[cur_arg];
3033
3034 if (!*(args[cur_arg+1]) ||
3035 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3036 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3037 goto next;
3038 }
3039 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3040 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3041
3042 /* Parse the value pattern, optional */
3043 if (strcmp(args[cur_arg+2], "-m") == 0) {
3044 cur_arg += 2;
3045 if (!*(args[cur_arg+1])) {
3046 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3047 args[orig_arg], args[cur_arg]);
3048 goto error;
3049 }
3050 if (strcmp(args[cur_arg+1], "str") == 0)
3051 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3052 else if (strcmp(args[cur_arg+1], "beg") == 0)
3053 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3054 else if (strcmp(args[cur_arg+1], "end") == 0)
3055 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3056 else if (strcmp(args[cur_arg+1], "sub") == 0)
3057 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3058 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3059 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3060 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3061 args[orig_arg]);
3062 goto error;
3063 }
3064 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3065 }
3066 else {
3067 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3068 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3069 goto error;
3070 }
3071 }
3072 else
3073 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3074
3075 if (!*(args[cur_arg+2])) {
3076 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3077 goto error;
3078 }
3079 vpat = args[cur_arg+2];
3080 cur_arg += 2;
3081 }
3082 else if (strcmp(args[cur_arg], "comment") == 0) {
3083 if (in_pattern) {
3084 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3085 goto error;
3086 }
3087 if (!*(args[cur_arg+1])) {
3088 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3089 goto error;
3090 }
3091 cur_arg++;
3092 free(comment);
3093 comment = strdup(args[cur_arg]);
3094 if (!comment) {
3095 memprintf(errmsg, "out of memory");
3096 goto error;
3097 }
3098 }
3099 else if (strcmp(args[cur_arg], "on-success") == 0) {
3100 if (in_pattern) {
3101 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3102 goto error;
3103 }
3104 if (!*(args[cur_arg+1])) {
3105 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3106 goto error;
3107 }
3108 cur_arg++;
3109 on_success_msg = args[cur_arg];
3110 }
3111 else if (strcmp(args[cur_arg], "on-error") == 0) {
3112 if (in_pattern) {
3113 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3114 goto error;
3115 }
3116 if (!*(args[cur_arg+1])) {
3117 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3118 goto error;
3119 }
3120 cur_arg++;
3121 on_error_msg = args[cur_arg];
3122 }
3123 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3124 if (in_pattern) {
3125 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3126 goto error;
3127 }
3128 if (!*(args[cur_arg+1])) {
3129 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3130 goto error;
3131 }
3132 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3133 ok_st = HCHK_STATUS_L7OKD;
3134 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3135 ok_st = HCHK_STATUS_L7OKCD;
3136 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3137 ok_st = HCHK_STATUS_L6OK;
3138 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3139 ok_st = HCHK_STATUS_L4OK;
3140 else {
3141 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3142 args[cur_arg], args[cur_arg+1]);
3143 goto error;
3144 }
3145 cur_arg++;
3146 }
3147 else if (strcmp(args[cur_arg], "error-status") == 0) {
3148 if (in_pattern) {
3149 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3150 goto error;
3151 }
3152 if (!*(args[cur_arg+1])) {
3153 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3154 goto error;
3155 }
3156 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3157 err_st = HCHK_STATUS_L7RSP;
3158 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3159 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003160 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3161 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003162 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3163 err_st = HCHK_STATUS_L6RSP;
3164 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3165 err_st = HCHK_STATUS_L4CON;
3166 else {
3167 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3168 args[cur_arg], args[cur_arg+1]);
3169 goto error;
3170 }
3171 cur_arg++;
3172 }
3173 else if (strcmp(args[cur_arg], "status-code") == 0) {
3174 int idx = 0;
3175
3176 if (in_pattern) {
3177 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3178 goto error;
3179 }
3180 if (!*(args[cur_arg+1])) {
3181 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3182 goto error;
3183 }
3184
3185 cur_arg++;
3186 release_sample_expr(status_expr);
3187 px->conf.args.ctx = ARGC_SRV;
3188 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3189 file, line, errmsg, &px->conf.args, NULL);
3190 if (!status_expr) {
3191 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3192 goto error;
3193 }
3194 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3195 memprintf(errmsg, "error detected while parsing status-code expression : "
3196 " fetch method '%s' extracts information from '%s', "
3197 "none of which is available here.\n",
3198 args[cur_arg], sample_src_names(status_expr->fetch->use));
3199 goto error;
3200 }
3201 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3202 }
3203 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3204 if (in_pattern) {
3205 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3206 goto error;
3207 }
3208 if (!*(args[cur_arg+1])) {
3209 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3210 goto error;
3211 }
3212 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3213 tout_st = HCHK_STATUS_L7TOUT;
3214 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3215 tout_st = HCHK_STATUS_L6TOUT;
3216 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3217 tout_st = HCHK_STATUS_L4TOUT;
3218 else {
3219 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3220 args[cur_arg], args[cur_arg+1]);
3221 goto error;
3222 }
3223 cur_arg++;
3224 }
3225 else {
3226 if (proto == TCPCHK_RULES_HTTP_CHK) {
3227 bad_http_kw:
3228 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3229 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3230 }
3231 else {
3232 bad_tcp_kw:
3233 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3234 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3235 }
3236 goto error;
3237 }
3238 next:
3239 cur_arg++;
3240 }
3241
3242 chk = calloc(1, sizeof(*chk));
3243 if (!chk) {
3244 memprintf(errmsg, "out of memory");
3245 goto error;
3246 }
3247 chk->action = TCPCHK_ACT_EXPECT;
3248 LIST_INIT(&chk->expect.onerror_fmt);
3249 LIST_INIT(&chk->expect.onsuccess_fmt);
3250 chk->comment = comment; comment = NULL;
3251 chk->expect.type = type;
3252 chk->expect.min_recv = min_recv;
3253 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3254 chk->expect.ok_status = ok_st;
3255 chk->expect.err_status = err_st;
3256 chk->expect.tout_status = tout_st;
3257 chk->expect.status_expr = status_expr; status_expr = NULL;
3258
3259 if (on_success_msg) {
3260 px->conf.args.ctx = ARGC_SRV;
3261 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3262 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3263 goto error;
3264 }
3265 }
3266 if (on_error_msg) {
3267 px->conf.args.ctx = ARGC_SRV;
3268 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3269 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3270 goto error;
3271 }
3272 }
3273
3274 switch (chk->expect.type) {
3275 case TCPCHK_EXPECT_HTTP_STATUS: {
3276 const char *p = pattern;
3277 unsigned int c1,c2;
3278
3279 chk->expect.codes.codes = NULL;
3280 chk->expect.codes.num = 0;
3281 while (1) {
3282 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3283 if (*p == '-') {
3284 p++;
3285 c2 = read_uint(&p, pattern + strlen(pattern));
3286 }
3287 if (c1 > c2) {
3288 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3289 goto error;
3290 }
3291
3292 chk->expect.codes.num++;
3293 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3294 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3295 if (!chk->expect.codes.codes) {
3296 memprintf(errmsg, "out of memory");
3297 goto error;
3298 }
3299 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3300 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3301
3302 if (*p == '\0')
3303 break;
3304 if (*p != ',') {
3305 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3306 goto error;
3307 }
3308 p++;
3309 }
3310 break;
3311 }
3312 case TCPCHK_EXPECT_STRING:
3313 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003314 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003315 if (!isttest(chk->expect.data)) {
3316 memprintf(errmsg, "out of memory");
3317 goto error;
3318 }
3319 break;
3320 case TCPCHK_EXPECT_BINARY: {
3321 int len = chk->expect.data.len;
3322
3323 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3324 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3325 goto error;
3326 }
3327 chk->expect.data.len = len;
3328 break;
3329 }
3330 case TCPCHK_EXPECT_STRING_REGEX:
3331 case TCPCHK_EXPECT_BINARY_REGEX:
3332 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3333 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3334 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3335 if (!chk->expect.regex)
3336 goto error;
3337 break;
3338
3339 case TCPCHK_EXPECT_STRING_LF:
3340 case TCPCHK_EXPECT_BINARY_LF:
3341 case TCPCHK_EXPECT_HTTP_BODY_LF:
3342 LIST_INIT(&chk->expect.fmt);
3343 px->conf.args.ctx = ARGC_SRV;
3344 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3345 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3346 goto error;
3347 }
3348 break;
3349
3350 case TCPCHK_EXPECT_HTTP_HEADER:
3351 if (!npat) {
3352 memprintf(errmsg, "unexpected error, undefined header name pattern");
3353 goto error;
3354 }
3355 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3356 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3357 if (!chk->expect.hdr.name_re)
3358 goto error;
3359 }
3360 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3361 px->conf.args.ctx = ARGC_SRV;
3362 LIST_INIT(&chk->expect.hdr.name_fmt);
3363 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3364 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3365 goto error;
3366 }
3367 }
3368 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003369 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003370 if (!isttest(chk->expect.hdr.name)) {
3371 memprintf(errmsg, "out of memory");
3372 goto error;
3373 }
3374 }
3375
3376 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3377 chk->expect.hdr.value = IST_NULL;
3378 break;
3379 }
3380
3381 if (!vpat) {
3382 memprintf(errmsg, "unexpected error, undefined header value pattern");
3383 goto error;
3384 }
3385 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3386 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3387 if (!chk->expect.hdr.value_re)
3388 goto error;
3389 }
3390 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3391 px->conf.args.ctx = ARGC_SRV;
3392 LIST_INIT(&chk->expect.hdr.value_fmt);
3393 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3394 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3395 goto error;
3396 }
3397 }
3398 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003399 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003400 if (!isttest(chk->expect.hdr.value)) {
3401 memprintf(errmsg, "out of memory");
3402 goto error;
3403 }
3404 }
3405
3406 break;
3407 case TCPCHK_EXPECT_CUSTOM:
3408 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3409 break;
3410 case TCPCHK_EXPECT_UNDEF:
3411 memprintf(errmsg, "pattern not found");
3412 goto error;
3413 }
3414
3415 /* All tcp-check expect points back to the first inverse expect rule in
3416 * a chain of one or more expect rule, potentially itself.
3417 */
3418 chk->expect.head = chk;
3419 list_for_each_entry_rev(prev_check, rules, list) {
3420 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3421 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3422 chk->expect.head = prev_check;
3423 continue;
3424 }
3425 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3426 break;
3427 }
3428 return chk;
3429
3430 error:
3431 free_tcpcheck(chk, 0);
3432 free(comment);
3433 release_sample_expr(status_expr);
3434 return NULL;
3435}
3436
3437/* Overwrites fields of the old http send rule with those of the new one. When
3438 * replaced, old values are freed and replaced by the new ones. New values are
3439 * not copied but transferred. At the end <new> should be empty and can be
3440 * safely released. This function never fails.
3441 */
3442void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3443{
3444 struct logformat_node *lf, *lfb;
3445 struct tcpcheck_http_hdr *hdr, *bhdr;
3446
3447
3448 if (new->send.http.meth.str.area) {
3449 free(old->send.http.meth.str.area);
3450 old->send.http.meth.meth = new->send.http.meth.meth;
3451 old->send.http.meth.str.area = new->send.http.meth.str.area;
3452 old->send.http.meth.str.data = new->send.http.meth.str.data;
3453 new->send.http.meth.str = BUF_NULL;
3454 }
3455
3456 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3457 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3458 istfree(&old->send.http.uri);
3459 else
3460 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3461 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3462 old->send.http.uri = new->send.http.uri;
3463 new->send.http.uri = IST_NULL;
3464 }
3465 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3466 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3467 istfree(&old->send.http.uri);
3468 else
3469 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3470 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3471 LIST_INIT(&old->send.http.uri_fmt);
3472 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003473 LIST_DELETE(&lf->list);
3474 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003475 }
3476 }
3477
3478 if (isttest(new->send.http.vsn)) {
3479 istfree(&old->send.http.vsn);
3480 old->send.http.vsn = new->send.http.vsn;
3481 new->send.http.vsn = IST_NULL;
3482 }
3483
3484 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3485 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003486 LIST_DELETE(&hdr->list);
3487 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003488 }
3489
3490 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3491 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3492 istfree(&old->send.http.body);
3493 else
3494 free_tcpcheck_fmt(&old->send.http.body_fmt);
3495 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3496 old->send.http.body = new->send.http.body;
3497 new->send.http.body = IST_NULL;
3498 }
3499 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3500 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3501 istfree(&old->send.http.body);
3502 else
3503 free_tcpcheck_fmt(&old->send.http.body_fmt);
3504 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3505 LIST_INIT(&old->send.http.body_fmt);
3506 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003507 LIST_DELETE(&lf->list);
3508 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003509 }
3510 }
3511}
3512
3513/* Internal function used to add an http-check rule in a list during the config
3514 * parsing step. Depending on its type, and the previously inserted rules, a
3515 * specific action may be performed or an error may be reported. This functions
3516 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3517 * message.
3518 */
3519int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3520{
3521 struct tcpcheck_rule *r;
3522
3523 /* the implicit send rule coming from an "option httpchk" line must be
3524 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003525 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003526 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003527 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003528 * sure the ruleset remains valid.
3529 */
3530
3531 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3532 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3533 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3534 * following tests are performed :
3535 *
3536 * 1- If there is no such rule or if it is not a send rule, the implicit send
3537 * rule is pushed in front of the ruleset
3538 *
3539 * 2- If it is another implicit send rule, it is replaced with the new one.
3540 *
3541 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3542 * both, overwriting the old send rule (the explicit one) with info of the
3543 * new send rule (the implicit one).
3544 */
3545 r = get_first_tcpcheck_rule(rules);
3546 if (r && r->action == TCPCHK_ACT_CONNECT)
3547 r = get_next_tcpcheck_rule(rules, r);
3548 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003549 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003550 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003551 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003552 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003553 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003554 }
3555 else {
3556 tcpcheck_overwrite_send_http_rule(r, chk);
3557 free_tcpcheck(chk, 0);
3558 }
3559 }
3560 else {
3561 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3562 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3563 * with an existing implicit send rule, if any. At the end, if there is no error,
3564 * the rule is appended to the list.
3565 */
3566
3567 r = get_last_tcpcheck_rule(rules);
3568 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3569 /* no error */;
3570 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3571 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3572 chk->index+1);
3573 return 0;
3574 }
3575 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3576 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3577 chk->index+1);
3578 return 0;
3579 }
3580 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3581 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3582 chk->index+1);
3583 return 0;
3584 }
3585
3586 if (chk->action == TCPCHK_ACT_SEND) {
3587 r = get_first_tcpcheck_rule(rules);
3588 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3589 tcpcheck_overwrite_send_http_rule(r, chk);
3590 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003591 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003592 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3593 chk = r;
3594 }
3595 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003596 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003597 }
3598 return 1;
3599}
3600
3601/* Check tcp-check health-check configuration for the proxy <px>. */
3602static int check_proxy_tcpcheck(struct proxy *px)
3603{
3604 struct tcpcheck_rule *chk, *back;
3605 char *comment = NULL, *errmsg = NULL;
3606 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003607 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003608
3609 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3610 deinit_proxy_tcpcheck(px);
3611 goto out;
3612 }
3613
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003614 ha_free(&px->check_command);
3615 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003616
3617 if (!px->tcpcheck_rules.list) {
3618 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
3619 ret |= ERR_ALERT | ERR_FATAL;
3620 goto out;
3621 }
3622
3623 /* HTTP ruleset only : */
3624 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3625 struct tcpcheck_rule *next;
3626
3627 /* move remaining implicit send rule from "option httpchk" line to the right place.
3628 * If such rule exists, it must be the first one. In this case, the rule is moved
3629 * after the first connect rule, if any. Otherwise, nothing is done.
3630 */
3631 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3632 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3633 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3634 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003635 LIST_DELETE(&chk->list);
3636 LIST_INSERT(&next->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003637 chk->index = next->index;
3638 }
3639 }
3640
3641 /* add implicit expect rule if the last one is a send. It is inherited from previous
3642 * versions where the http expect rule was optional. Now it is possible to chained
3643 * send/expect rules but the last expect may still be implicit.
3644 */
3645 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3646 if (chk && chk->action == TCPCHK_ACT_SEND) {
3647 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3648 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3649 px->conf.file, px->conf.line, &errmsg);
3650 if (!next) {
3651 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
3652 "(%s).\n", px->id, errmsg);
3653 free(errmsg);
3654 ret |= ERR_ALERT | ERR_FATAL;
3655 goto out;
3656 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003657 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003658 next->index = chk->index;
3659 }
3660 }
3661
3662 /* For all ruleset: */
3663
3664 /* If there is no connect rule preceding all send / expect rules, an
3665 * implicit one is inserted before all others.
3666 */
3667 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3668 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3669 chk = calloc(1, sizeof(*chk));
3670 if (!chk) {
3671 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
3672 "(out of memory).\n", px->id);
3673 ret |= ERR_ALERT | ERR_FATAL;
3674 goto out;
3675 }
3676 chk->action = TCPCHK_ACT_CONNECT;
3677 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003678 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003679 }
3680
3681 /* Remove all comment rules. To do so, when a such rule is found, the
3682 * comment is assigned to the following rule(s).
3683 */
3684 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003685 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3686 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003687
3688 prev_action = chk->action;
3689 switch (chk->action) {
3690 case TCPCHK_ACT_COMMENT:
3691 free(comment);
3692 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003693 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003694 free(chk);
3695 break;
3696 case TCPCHK_ACT_CONNECT:
3697 if (!chk->comment && comment)
3698 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003699 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003700 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003701 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003702 break;
3703 case TCPCHK_ACT_SEND:
3704 case TCPCHK_ACT_EXPECT:
3705 if (!chk->comment && comment)
3706 chk->comment = strdup(comment);
3707 break;
3708 }
3709 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003710 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003711
3712 out:
3713 return ret;
3714}
3715
3716void deinit_proxy_tcpcheck(struct proxy *px)
3717{
3718 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3719 px->tcpcheck_rules.flags = 0;
3720 px->tcpcheck_rules.list = NULL;
3721}
3722
3723static void deinit_tcpchecks()
3724{
3725 struct tcpcheck_ruleset *rs;
3726 struct tcpcheck_rule *r, *rb;
3727 struct ebpt_node *node, *next;
3728
3729 node = ebpt_first(&shared_tcpchecks);
3730 while (node) {
3731 next = ebpt_next(node);
3732 ebpt_delete(node);
3733 free(node->key);
3734 rs = container_of(node, typeof(*rs), node);
3735 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003736 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003737 free_tcpcheck(r, 0);
3738 }
3739 free(rs);
3740 node = next;
3741 }
3742}
3743
3744int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3745{
3746 struct tcpcheck_rule *tcpcheck, *prev_check;
3747 struct tcpcheck_expect *expect;
3748
Willy Tarreau6922e552021-03-22 21:11:45 +01003749 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003750 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003751 tcpcheck->action = TCPCHK_ACT_EXPECT;
3752
3753 expect = &tcpcheck->expect;
3754 expect->type = TCPCHK_EXPECT_STRING;
3755 LIST_INIT(&expect->onerror_fmt);
3756 LIST_INIT(&expect->onsuccess_fmt);
3757 expect->ok_status = HCHK_STATUS_L7OKD;
3758 expect->err_status = HCHK_STATUS_L7RSP;
3759 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003760 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003761 if (!isttest(expect->data)) {
3762 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3763 return 0;
3764 }
3765
3766 /* All tcp-check expect points back to the first inverse expect rule
3767 * in a chain of one or more expect rule, potentially itself.
3768 */
3769 tcpcheck->expect.head = tcpcheck;
3770 list_for_each_entry_rev(prev_check, rules->list, list) {
3771 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3772 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3773 tcpcheck->expect.head = prev_check;
3774 continue;
3775 }
3776 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3777 break;
3778 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003779 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003780 return 1;
3781}
3782
3783int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3784{
3785 struct tcpcheck_rule *tcpcheck;
3786 struct tcpcheck_send *send;
3787 const char *in;
3788 char *dst;
3789 int i;
3790
Willy Tarreau6922e552021-03-22 21:11:45 +01003791 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003792 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003793 tcpcheck->action = TCPCHK_ACT_SEND;
3794
3795 send = &tcpcheck->send;
3796 send->type = TCPCHK_SEND_STRING;
3797
3798 for (i = 0; strs[i]; i++)
3799 send->data.len += strlen(strs[i]);
3800
3801 send->data.ptr = malloc(istlen(send->data) + 1);
3802 if (!isttest(send->data)) {
3803 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3804 return 0;
3805 }
3806
3807 dst = istptr(send->data);
3808 for (i = 0; strs[i]; i++)
3809 for (in = strs[i]; (*dst = *in++); dst++);
3810 *dst = 0;
3811
Willy Tarreau2b718102021-04-21 07:32:39 +02003812 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003813 return 1;
3814}
3815
3816/* Parses the "tcp-check" proxy keyword */
3817static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003818 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003819 char **errmsg)
3820{
3821 struct tcpcheck_ruleset *rs = NULL;
3822 struct tcpcheck_rule *chk = NULL;
3823 int index, cur_arg, ret = 0;
3824
3825 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3826 ret = 1;
3827
3828 /* Deduce the ruleset name from the proxy info */
3829 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3830 ((curpx == defpx) ? "defaults" : curpx->id),
3831 curpx->conf.file, curpx->conf.line);
3832
3833 rs = find_tcpcheck_ruleset(b_orig(&trash));
3834 if (rs == NULL) {
3835 rs = create_tcpcheck_ruleset(b_orig(&trash));
3836 if (rs == NULL) {
3837 memprintf(errmsg, "out of memory.\n");
3838 goto error;
3839 }
3840 }
3841
3842 index = 0;
3843 if (!LIST_ISEMPTY(&rs->rules)) {
3844 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3845 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003846 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003847 }
3848
3849 cur_arg = 1;
3850 if (strcmp(args[cur_arg], "connect") == 0)
3851 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3852 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3853 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3854 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3855 else if (strcmp(args[cur_arg], "expect") == 0)
3856 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3857 else if (strcmp(args[cur_arg], "comment") == 0)
3858 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3859 else {
3860 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3861
3862 if (!kw) {
3863 action_kw_tcp_check_build_list(&trash);
3864 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3865 "%s%s. but got '%s'",
3866 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3867 goto error;
3868 }
3869 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3870 }
3871
3872 if (!chk) {
3873 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3874 goto error;
3875 }
3876 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3877
3878 /* No error: add the tcp-check rule in the list */
3879 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003880 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003881
3882 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3883 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3884 /* Use this ruleset if the proxy already has tcp-check enabled */
3885 curpx->tcpcheck_rules.list = &rs->rules;
3886 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3887 }
3888 else {
3889 /* mark this ruleset as unused for now */
3890 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3891 }
3892
3893 return ret;
3894
3895 error:
3896 free_tcpcheck(chk, 0);
3897 free_tcpcheck_ruleset(rs);
3898 return -1;
3899}
3900
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003901/* Parses the "http-check" proxy keyword */
3902static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003903 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003904 char **errmsg)
3905{
3906 struct tcpcheck_ruleset *rs = NULL;
3907 struct tcpcheck_rule *chk = NULL;
3908 int index, cur_arg, ret = 0;
3909
3910 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3911 ret = 1;
3912
3913 cur_arg = 1;
3914 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3915 /* enable a graceful server shutdown on an HTTP 404 response */
3916 curpx->options |= PR_O_DISABLE404;
3917 if (too_many_args(1, args, errmsg, NULL))
3918 goto error;
3919 goto out;
3920 }
3921 else if (strcmp(args[cur_arg], "send-state") == 0) {
3922 /* enable emission of the apparent state of a server in HTTP checks */
3923 curpx->options2 |= PR_O2_CHK_SNDST;
3924 if (too_many_args(1, args, errmsg, NULL))
3925 goto error;
3926 goto out;
3927 }
3928
3929 /* Deduce the ruleset name from the proxy info */
3930 chunk_printf(&trash, "*http-check-%s_%s-%d",
3931 ((curpx == defpx) ? "defaults" : curpx->id),
3932 curpx->conf.file, curpx->conf.line);
3933
3934 rs = find_tcpcheck_ruleset(b_orig(&trash));
3935 if (rs == NULL) {
3936 rs = create_tcpcheck_ruleset(b_orig(&trash));
3937 if (rs == NULL) {
3938 memprintf(errmsg, "out of memory.\n");
3939 goto error;
3940 }
3941 }
3942
3943 index = 0;
3944 if (!LIST_ISEMPTY(&rs->rules)) {
3945 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3946 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
3947 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003948 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003949 }
3950
3951 if (strcmp(args[cur_arg], "connect") == 0)
3952 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3953 else if (strcmp(args[cur_arg], "send") == 0)
3954 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3955 else if (strcmp(args[cur_arg], "expect") == 0)
3956 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
3957 file, line, errmsg);
3958 else if (strcmp(args[cur_arg], "comment") == 0)
3959 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3960 else {
3961 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3962
3963 if (!kw) {
3964 action_kw_tcp_check_build_list(&trash);
3965 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
3966 " 'send', 'expect'%s%s. but got '%s'",
3967 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3968 goto error;
3969 }
3970 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3971 }
3972
3973 if (!chk) {
3974 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3975 goto error;
3976 }
3977 ret = (*errmsg != NULL); /* Handle warning */
3978
3979 chk->index = index;
3980 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3981 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3982 /* Use this ruleset if the proxy already has http-check enabled */
3983 curpx->tcpcheck_rules.list = &rs->rules;
3984 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
3985 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
3986 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3987 curpx->tcpcheck_rules.list = NULL;
3988 goto error;
3989 }
3990 }
3991 else {
3992 /* mark this ruleset as unused for now */
3993 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02003994 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003995 }
3996
3997 out:
3998 return ret;
3999
4000 error:
4001 free_tcpcheck(chk, 0);
4002 free_tcpcheck_ruleset(rs);
4003 return -1;
4004}
4005
4006/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004007int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004008 const char *file, int line)
4009{
4010 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4011 static char *redis_res = "+PONG\r\n";
4012
4013 struct tcpcheck_ruleset *rs = NULL;
4014 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4015 struct tcpcheck_rule *chk;
4016 char *errmsg = NULL;
4017 int err_code = 0;
4018
4019 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4020 err_code |= ERR_WARN;
4021
4022 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4023 goto out;
4024
4025 curpx->options2 &= ~PR_O2_CHK_ANY;
4026 curpx->options2 |= PR_O2_TCPCHK_CHK;
4027
4028 free_tcpcheck_vars(&rules->preset_vars);
4029 rules->list = NULL;
4030 rules->flags = 0;
4031
4032 rs = find_tcpcheck_ruleset("*redis-check");
4033 if (rs)
4034 goto ruleset_found;
4035
4036 rs = create_tcpcheck_ruleset("*redis-check");
4037 if (rs == NULL) {
4038 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4039 goto error;
4040 }
4041
4042 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4043 1, curpx, &rs->rules, file, line, &errmsg);
4044 if (!chk) {
4045 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4046 goto error;
4047 }
4048 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004049 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004050
4051 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4052 "error-status", "L7STS",
4053 "on-error", "%[res.payload(0,0),cut_crlf]",
4054 "on-success", "Redis server is ok",
4055 ""},
4056 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4057 if (!chk) {
4058 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4059 goto error;
4060 }
4061 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004062 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004063
4064 ruleset_found:
4065 rules->list = &rs->rules;
4066 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4067 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4068
4069 out:
4070 free(errmsg);
4071 return err_code;
4072
4073 error:
4074 free_tcpcheck_ruleset(rs);
4075 err_code |= ERR_ALERT | ERR_FATAL;
4076 goto out;
4077}
4078
4079
4080/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004081int 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 +01004082 const char *file, int line)
4083{
4084 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4085 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4086 *
4087 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4088 */
4089 static char sslv3_client_hello[] = {
4090 "16" /* ContentType : 0x16 = Handshake */
4091 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4092 "0079" /* ContentLength : 0x79 bytes after this one */
4093 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4094 "000075" /* HandshakeLength : 0x75 bytes after this one */
4095 "0300" /* Hello Version : 0x0300 = v3 */
4096 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4097 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4098 "00" /* Session ID length : empty (no session ID) */
4099 "004E" /* Cipher Suite Length : 78 bytes after this one */
4100 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4101 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4102 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4103 "000D" "000E" "000F" "0010" /* various bit lengths, */
4104 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4105 "0015" "0016" "0017" "0018"
4106 "0019" "001A" "001B" "002F"
4107 "0030" "0031" "0032" "0033"
4108 "0034" "0035" "0036" "0037"
4109 "0038" "0039" "003A"
4110 "01" /* Compression Length : 0x01 = 1 byte for types */
4111 "00" /* Compression Type : 0x00 = NULL compression */
4112 };
4113
4114 struct tcpcheck_ruleset *rs = NULL;
4115 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4116 struct tcpcheck_rule *chk;
4117 char *errmsg = NULL;
4118 int err_code = 0;
4119
4120 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4121 err_code |= ERR_WARN;
4122
4123 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4124 goto out;
4125
4126 curpx->options2 &= ~PR_O2_CHK_ANY;
4127 curpx->options2 |= PR_O2_TCPCHK_CHK;
4128
4129 free_tcpcheck_vars(&rules->preset_vars);
4130 rules->list = NULL;
4131 rules->flags = 0;
4132
4133 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4134 if (rs)
4135 goto ruleset_found;
4136
4137 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4138 if (rs == NULL) {
4139 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4140 goto error;
4141 }
4142
4143 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4144 1, curpx, &rs->rules, file, line, &errmsg);
4145 if (!chk) {
4146 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4147 goto error;
4148 }
4149 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004150 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004151
4152 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4153 "min-recv", "5", "ok-status", "L6OK",
4154 "error-status", "L6RSP", "tout-status", "L6TOUT",
4155 ""},
4156 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4157 if (!chk) {
4158 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4159 goto error;
4160 }
4161 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004162 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004163
4164 ruleset_found:
4165 rules->list = &rs->rules;
4166 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4167 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4168
4169 out:
4170 free(errmsg);
4171 return err_code;
4172
4173 error:
4174 free_tcpcheck_ruleset(rs);
4175 err_code |= ERR_ALERT | ERR_FATAL;
4176 goto out;
4177}
4178
4179/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004180int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004181 const char *file, int line)
4182{
4183 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4184
4185 struct tcpcheck_ruleset *rs = NULL;
4186 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4187 struct tcpcheck_rule *chk;
4188 struct tcpcheck_var *var = NULL;
4189 char *cmd = NULL, *errmsg = NULL;
4190 int err_code = 0;
4191
4192 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4193 err_code |= ERR_WARN;
4194
4195 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4196 goto out;
4197
4198 curpx->options2 &= ~PR_O2_CHK_ANY;
4199 curpx->options2 |= PR_O2_TCPCHK_CHK;
4200
4201 free_tcpcheck_vars(&rules->preset_vars);
4202 rules->list = NULL;
4203 rules->flags = 0;
4204
4205 cur_arg += 2;
4206 if (*args[cur_arg] && *args[cur_arg+1] &&
4207 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4208 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4209 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4210 if (cmd)
4211 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4212 }
4213 else {
4214 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4215 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4216 cmd = strdup("HELO localhost");
4217 }
4218
4219 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4220 if (cmd == NULL || var == NULL) {
4221 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4222 goto error;
4223 }
4224 var->data.type = SMP_T_STR;
4225 var->data.u.str.area = cmd;
4226 var->data.u.str.data = strlen(cmd);
4227 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004228 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004229 cmd = NULL;
4230 var = NULL;
4231
4232 rs = find_tcpcheck_ruleset("*smtp-check");
4233 if (rs)
4234 goto ruleset_found;
4235
4236 rs = create_tcpcheck_ruleset("*smtp-check");
4237 if (rs == NULL) {
4238 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4239 goto error;
4240 }
4241
4242 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4243 1, curpx, &rs->rules, file, line, &errmsg);
4244 if (!chk) {
4245 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4246 goto error;
4247 }
4248 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004249 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004250
4251 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4252 "min-recv", "4",
4253 "error-status", "L7RSP",
4254 "on-error", "%[res.payload(0,0),cut_crlf]",
4255 ""},
4256 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4257 if (!chk) {
4258 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4259 goto error;
4260 }
4261 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004262 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004263
4264 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4265 "min-recv", "4",
4266 "error-status", "L7STS",
4267 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4268 "status-code", "res.payload(0,3)",
4269 ""},
4270 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4271 if (!chk) {
4272 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4273 goto error;
4274 }
4275 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004276 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004277
4278 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4279 1, curpx, &rs->rules, file, line, &errmsg);
4280 if (!chk) {
4281 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4282 goto error;
4283 }
4284 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004285 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004286
4287 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4288 "min-recv", "4",
4289 "error-status", "L7STS",
4290 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4291 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4292 "status-code", "res.payload(0,3)",
4293 ""},
4294 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4295 if (!chk) {
4296 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4297 goto error;
4298 }
4299 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004300 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004301
4302 ruleset_found:
4303 rules->list = &rs->rules;
4304 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4305 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4306
4307 out:
4308 free(errmsg);
4309 return err_code;
4310
4311 error:
4312 free(cmd);
4313 free(var);
4314 free_tcpcheck_vars(&rules->preset_vars);
4315 free_tcpcheck_ruleset(rs);
4316 err_code |= ERR_ALERT | ERR_FATAL;
4317 goto out;
4318}
4319
4320/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004321int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004322 const char *file, int line)
4323{
4324 static char pgsql_req[] = {
4325 "%[var(check.plen),htonl,hex]" /* The packet length*/
4326 "00030000" /* the version 3.0 */
4327 "7573657200" /* "user" key */
4328 "%[var(check.username),hex]00" /* the username */
4329 "00"
4330 };
4331
4332 struct tcpcheck_ruleset *rs = NULL;
4333 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4334 struct tcpcheck_rule *chk;
4335 struct tcpcheck_var *var = NULL;
4336 char *user = NULL, *errmsg = NULL;
4337 size_t packetlen = 0;
4338 int err_code = 0;
4339
4340 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4341 err_code |= ERR_WARN;
4342
4343 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4344 goto out;
4345
4346 curpx->options2 &= ~PR_O2_CHK_ANY;
4347 curpx->options2 |= PR_O2_TCPCHK_CHK;
4348
4349 free_tcpcheck_vars(&rules->preset_vars);
4350 rules->list = NULL;
4351 rules->flags = 0;
4352
4353 cur_arg += 2;
4354 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4355 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4356 file, line, args[0], args[1]);
4357 goto error;
4358 }
4359 if (strcmp(args[cur_arg], "user") == 0) {
4360 packetlen = 15 + strlen(args[cur_arg+1]);
4361 user = strdup(args[cur_arg+1]);
4362
4363 var = create_tcpcheck_var(ist("check.username"));
4364 if (user == NULL || var == NULL) {
4365 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4366 goto error;
4367 }
4368 var->data.type = SMP_T_STR;
4369 var->data.u.str.area = user;
4370 var->data.u.str.data = strlen(user);
4371 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004372 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004373 user = NULL;
4374 var = NULL;
4375
4376 var = create_tcpcheck_var(ist("check.plen"));
4377 if (var == NULL) {
4378 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4379 goto error;
4380 }
4381 var->data.type = SMP_T_SINT;
4382 var->data.u.sint = packetlen;
4383 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004384 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004385 var = NULL;
4386 }
4387 else {
4388 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4389 file, line, args[0], args[1]);
4390 goto error;
4391 }
4392
4393 rs = find_tcpcheck_ruleset("*pgsql-check");
4394 if (rs)
4395 goto ruleset_found;
4396
4397 rs = create_tcpcheck_ruleset("*pgsql-check");
4398 if (rs == NULL) {
4399 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4400 goto error;
4401 }
4402
4403 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4404 1, curpx, &rs->rules, file, line, &errmsg);
4405 if (!chk) {
4406 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4407 goto error;
4408 }
4409 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004410 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004411
4412 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4413 1, curpx, &rs->rules, file, line, &errmsg);
4414 if (!chk) {
4415 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4416 goto error;
4417 }
4418 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004419 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004420
4421 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4422 "min-recv", "5",
4423 "error-status", "L7RSP",
4424 "on-error", "%[res.payload(6,0)]",
4425 ""},
4426 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4427 if (!chk) {
4428 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4429 goto error;
4430 }
4431 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004432 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004433
4434 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4435 "min-recv", "9",
4436 "error-status", "L7STS",
4437 "on-success", "PostgreSQL server is ok",
4438 "on-error", "PostgreSQL unknown error",
4439 ""},
4440 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4441 if (!chk) {
4442 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4443 goto error;
4444 }
4445 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004446 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004447
4448 ruleset_found:
4449 rules->list = &rs->rules;
4450 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4451 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4452
4453 out:
4454 free(errmsg);
4455 return err_code;
4456
4457 error:
4458 free(user);
4459 free(var);
4460 free_tcpcheck_vars(&rules->preset_vars);
4461 free_tcpcheck_ruleset(rs);
4462 err_code |= ERR_ALERT | ERR_FATAL;
4463 goto out;
4464}
4465
4466
4467/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004468int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004469 const char *file, int line)
4470{
4471 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4472 * const char mysql40_client_auth_pkt[] = {
4473 * "\x0e\x00\x00" // packet length
4474 * "\x01" // packet number
4475 * "\x00\x00" // client capabilities
4476 * "\x00\x00\x01" // max packet
4477 * "haproxy\x00" // username (null terminated string)
4478 * "\x00" // filler (always 0x00)
4479 * "\x01\x00\x00" // packet length
4480 * "\x00" // packet number
4481 * "\x01" // COM_QUIT command
4482 * };
4483 */
4484 static char mysql40_rsname[] = "*mysql40-check";
4485 static char mysql40_req[] = {
4486 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4487 "0080" /* client capabilities */
4488 "000001" /* max packet */
4489 "%[var(check.username),hex]00" /* the username */
4490 "00" /* filler (always 0x00) */
4491 "010000" /* packet length*/
4492 "00" /* sequence ID */
4493 "01" /* COM_QUIT command */
4494 };
4495
4496 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4497 * const char mysql41_client_auth_pkt[] = {
4498 * "\x0e\x00\x00\" // packet length
4499 * "\x01" // packet number
4500 * "\x00\x00\x00\x00" // client capabilities
4501 * "\x00\x00\x00\x01" // max packet
4502 * "\x21" // character set (UTF-8)
4503 * char[23] // All zeroes
4504 * "haproxy\x00" // username (null terminated string)
4505 * "\x00" // filler (always 0x00)
4506 * "\x01\x00\x00" // packet length
4507 * "\x00" // packet number
4508 * "\x01" // COM_QUIT command
4509 * };
4510 */
4511 static char mysql41_rsname[] = "*mysql41-check";
4512 static char mysql41_req[] = {
4513 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4514 "00820000" /* client capabilities */
4515 "00800001" /* max packet */
4516 "21" /* character set (UTF-8) */
4517 "000000000000000000000000" /* 23 bytes, al zeroes */
4518 "0000000000000000000000"
4519 "%[var(check.username),hex]00" /* the username */
4520 "00" /* filler (always 0x00) */
4521 "010000" /* packet length*/
4522 "00" /* sequence ID */
4523 "01" /* COM_QUIT command */
4524 };
4525
4526 struct tcpcheck_ruleset *rs = NULL;
4527 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4528 struct tcpcheck_rule *chk;
4529 struct tcpcheck_var *var = NULL;
4530 char *mysql_rsname = "*mysql-check";
4531 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4532 int index = 0, err_code = 0;
4533
4534 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4535 err_code |= ERR_WARN;
4536
4537 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4538 goto out;
4539
4540 curpx->options2 &= ~PR_O2_CHK_ANY;
4541 curpx->options2 |= PR_O2_TCPCHK_CHK;
4542
4543 free_tcpcheck_vars(&rules->preset_vars);
4544 rules->list = NULL;
4545 rules->flags = 0;
4546
4547 cur_arg += 2;
4548 if (*args[cur_arg]) {
4549 int packetlen, userlen;
4550
4551 if (strcmp(args[cur_arg], "user") != 0) {
4552 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4553 file, line, args[0], args[1], args[cur_arg]);
4554 goto error;
4555 }
4556
4557 if (*(args[cur_arg+1]) == 0) {
4558 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4559 file, line, args[0], args[1], args[cur_arg]);
4560 goto error;
4561 }
4562
4563 hdr = calloc(4, sizeof(*hdr));
4564 user = strdup(args[cur_arg+1]);
4565 userlen = strlen(args[cur_arg+1]);
4566
4567 if (hdr == NULL || user == NULL) {
4568 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4569 goto error;
4570 }
4571
4572 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4573 packetlen = userlen + 7 + 27;
4574 mysql_req = mysql41_req;
4575 mysql_rsname = mysql41_rsname;
4576 }
4577 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4578 packetlen = userlen + 7;
4579 mysql_req = mysql40_req;
4580 mysql_rsname = mysql40_rsname;
4581 }
4582 else {
4583 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4584 file, line, args[cur_arg], args[cur_arg+2]);
4585 goto error;
4586 }
4587
4588 hdr[0] = (unsigned char)(packetlen & 0xff);
4589 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4590 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4591 hdr[3] = 1;
4592
4593 var = create_tcpcheck_var(ist("check.header"));
4594 if (var == NULL) {
4595 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4596 goto error;
4597 }
4598 var->data.type = SMP_T_STR;
4599 var->data.u.str.area = hdr;
4600 var->data.u.str.data = 4;
4601 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004602 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004603 hdr = NULL;
4604 var = NULL;
4605
4606 var = create_tcpcheck_var(ist("check.username"));
4607 if (var == NULL) {
4608 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4609 goto error;
4610 }
4611 var->data.type = SMP_T_STR;
4612 var->data.u.str.area = user;
4613 var->data.u.str.data = strlen(user);
4614 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004615 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004616 user = NULL;
4617 var = NULL;
4618 }
4619
4620 rs = find_tcpcheck_ruleset(mysql_rsname);
4621 if (rs)
4622 goto ruleset_found;
4623
4624 rs = create_tcpcheck_ruleset(mysql_rsname);
4625 if (rs == NULL) {
4626 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4627 goto error;
4628 }
4629
4630 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4631 1, curpx, &rs->rules, file, line, &errmsg);
4632 if (!chk) {
4633 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4634 goto error;
4635 }
4636 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004637 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004638
4639 if (mysql_req) {
4640 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4641 1, curpx, &rs->rules, file, line, &errmsg);
4642 if (!chk) {
4643 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4644 goto error;
4645 }
4646 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004647 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004648 }
4649
4650 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4651 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4652 if (!chk) {
4653 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4654 goto error;
4655 }
4656 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4657 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004658 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004659
4660 if (mysql_req) {
4661 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4662 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4663 if (!chk) {
4664 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4665 goto error;
4666 }
4667 chk->expect.custom = tcpcheck_mysql_expect_ok;
4668 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004669 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004670 }
4671
4672 ruleset_found:
4673 rules->list = &rs->rules;
4674 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4675 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4676
4677 out:
4678 free(errmsg);
4679 return err_code;
4680
4681 error:
4682 free(hdr);
4683 free(user);
4684 free(var);
4685 free_tcpcheck_vars(&rules->preset_vars);
4686 free_tcpcheck_ruleset(rs);
4687 err_code |= ERR_ALERT | ERR_FATAL;
4688 goto out;
4689}
4690
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004691int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004692 const char *file, int line)
4693{
4694 static char *ldap_req = "300C020101600702010304008000";
4695
4696 struct tcpcheck_ruleset *rs = NULL;
4697 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4698 struct tcpcheck_rule *chk;
4699 char *errmsg = NULL;
4700 int err_code = 0;
4701
4702 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4703 err_code |= ERR_WARN;
4704
4705 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4706 goto out;
4707
4708 curpx->options2 &= ~PR_O2_CHK_ANY;
4709 curpx->options2 |= PR_O2_TCPCHK_CHK;
4710
4711 free_tcpcheck_vars(&rules->preset_vars);
4712 rules->list = NULL;
4713 rules->flags = 0;
4714
4715 rs = find_tcpcheck_ruleset("*ldap-check");
4716 if (rs)
4717 goto ruleset_found;
4718
4719 rs = create_tcpcheck_ruleset("*ldap-check");
4720 if (rs == NULL) {
4721 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4722 goto error;
4723 }
4724
4725 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4726 1, curpx, &rs->rules, file, line, &errmsg);
4727 if (!chk) {
4728 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4729 goto error;
4730 }
4731 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004732 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004733
4734 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4735 "min-recv", "14",
4736 "on-error", "Not LDAPv3 protocol",
4737 ""},
4738 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4739 if (!chk) {
4740 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4741 goto error;
4742 }
4743 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004744 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004745
4746 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4747 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4748 if (!chk) {
4749 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4750 goto error;
4751 }
4752 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4753 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004754 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004755
4756 ruleset_found:
4757 rules->list = &rs->rules;
4758 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4759 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4760
4761 out:
4762 free(errmsg);
4763 return err_code;
4764
4765 error:
4766 free_tcpcheck_ruleset(rs);
4767 err_code |= ERR_ALERT | ERR_FATAL;
4768 goto out;
4769}
4770
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004771int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004772 const char *file, int line)
4773{
4774 struct tcpcheck_ruleset *rs = NULL;
4775 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4776 struct tcpcheck_rule *chk;
4777 char *spop_req = NULL;
4778 char *errmsg = NULL;
4779 int spop_len = 0, err_code = 0;
4780
4781 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4782 err_code |= ERR_WARN;
4783
4784 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4785 goto out;
4786
4787 curpx->options2 &= ~PR_O2_CHK_ANY;
4788 curpx->options2 |= PR_O2_TCPCHK_CHK;
4789
4790 free_tcpcheck_vars(&rules->preset_vars);
4791 rules->list = NULL;
4792 rules->flags = 0;
4793
4794
4795 rs = find_tcpcheck_ruleset("*spop-check");
4796 if (rs)
4797 goto ruleset_found;
4798
4799 rs = create_tcpcheck_ruleset("*spop-check");
4800 if (rs == NULL) {
4801 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4802 goto error;
4803 }
4804
4805 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4806 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4807 goto error;
4808 }
4809 chunk_reset(&trash);
4810 dump_binary(&trash, spop_req, spop_len);
4811 trash.area[trash.data] = '\0';
4812
4813 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4814 1, curpx, &rs->rules, file, line, &errmsg);
4815 if (!chk) {
4816 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4817 goto error;
4818 }
4819 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004820 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004821
4822 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4823 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4824 if (!chk) {
4825 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4826 goto error;
4827 }
4828 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4829 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004830 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004831
4832 ruleset_found:
4833 rules->list = &rs->rules;
4834 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4835 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4836
4837 out:
4838 free(spop_req);
4839 free(errmsg);
4840 return err_code;
4841
4842 error:
4843 free_tcpcheck_ruleset(rs);
4844 err_code |= ERR_ALERT | ERR_FATAL;
4845 goto out;
4846}
4847
4848
4849static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4850{
4851 struct tcpcheck_rule *chk = NULL;
4852 struct tcpcheck_http_hdr *hdr = NULL;
4853 char *meth = NULL, *uri = NULL, *vsn = NULL;
4854 char *hdrs, *body;
4855
4856 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4857 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4858 if (hdrs == body)
4859 hdrs = NULL;
4860 if (hdrs) {
4861 *hdrs = '\0';
4862 hdrs +=2;
4863 }
4864 if (body) {
4865 *body = '\0';
4866 body += 4;
4867 }
4868 if (hdrs || body) {
4869 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4870 " Please, consider to use 'http-check send' directive instead.");
4871 }
4872
4873 chk = calloc(1, sizeof(*chk));
4874 if (!chk) {
4875 memprintf(errmsg, "out of memory");
4876 goto error;
4877 }
4878 chk->action = TCPCHK_ACT_SEND;
4879 chk->send.type = TCPCHK_SEND_HTTP;
4880 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4881 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4882 LIST_INIT(&chk->send.http.hdrs);
4883
4884 /* Copy the method, uri and version */
4885 if (*args[cur_arg]) {
4886 if (!*args[cur_arg+1])
4887 uri = args[cur_arg];
4888 else
4889 meth = args[cur_arg];
4890 }
4891 if (*args[cur_arg+1])
4892 uri = args[cur_arg+1];
4893 if (*args[cur_arg+2])
4894 vsn = args[cur_arg+2];
4895
4896 if (meth) {
4897 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4898 chk->send.http.meth.str.area = strdup(meth);
4899 chk->send.http.meth.str.data = strlen(meth);
4900 if (!chk->send.http.meth.str.area) {
4901 memprintf(errmsg, "out of memory");
4902 goto error;
4903 }
4904 }
4905 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004906 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004907 if (!isttest(chk->send.http.uri)) {
4908 memprintf(errmsg, "out of memory");
4909 goto error;
4910 }
4911 }
4912 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004913 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004914 if (!isttest(chk->send.http.vsn)) {
4915 memprintf(errmsg, "out of memory");
4916 goto error;
4917 }
4918 }
4919
4920 /* Copy the header */
4921 if (hdrs) {
4922 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4923 struct h1m h1m;
4924 int i, ret;
4925
4926 /* Build and parse the request */
4927 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4928
4929 h1m.flags = H1_MF_HDRS_ONLY;
4930 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4931 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4932 &h1m, NULL);
4933 if (ret <= 0) {
4934 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4935 goto error;
4936 }
4937
4938 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4939 hdr = calloc(1, sizeof(*hdr));
4940 if (!hdr) {
4941 memprintf(errmsg, "out of memory");
4942 goto error;
4943 }
4944 LIST_INIT(&hdr->value);
4945 hdr->name = istdup(tmp_hdrs[i].n);
4946 if (!hdr->name.ptr) {
4947 memprintf(errmsg, "out of memory");
4948 goto error;
4949 }
4950
4951 ist0(tmp_hdrs[i].v);
4952 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
4953 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02004954 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004955 }
4956 }
4957
4958 /* Copy the body */
4959 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004960 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004961 if (!isttest(chk->send.http.body)) {
4962 memprintf(errmsg, "out of memory");
4963 goto error;
4964 }
4965 }
4966
4967 return chk;
4968
4969 error:
4970 free_tcpcheck_http_hdr(hdr);
4971 free_tcpcheck(chk, 0);
4972 return NULL;
4973}
4974
4975/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004976int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004977 const char *file, int line)
4978{
4979 struct tcpcheck_ruleset *rs = NULL;
4980 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4981 struct tcpcheck_rule *chk;
4982 char *errmsg = NULL;
4983 int err_code = 0;
4984
4985 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4986 err_code |= ERR_WARN;
4987
4988 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4989 goto out;
4990
4991 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
4992 if (!chk) {
4993 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
4994 goto error;
4995 }
4996 if (errmsg) {
4997 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
4998 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01004999 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005000 }
5001
5002 no_request:
5003 curpx->options2 &= ~PR_O2_CHK_ANY;
5004 curpx->options2 |= PR_O2_TCPCHK_CHK;
5005
5006 free_tcpcheck_vars(&rules->preset_vars);
5007 rules->list = NULL;
5008 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5009
5010 /* Deduce the ruleset name from the proxy info */
5011 chunk_printf(&trash, "*http-check-%s_%s-%d",
5012 ((curpx == defpx) ? "defaults" : curpx->id),
5013 curpx->conf.file, curpx->conf.line);
5014
5015 rs = find_tcpcheck_ruleset(b_orig(&trash));
5016 if (rs == NULL) {
5017 rs = create_tcpcheck_ruleset(b_orig(&trash));
5018 if (rs == NULL) {
5019 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5020 goto error;
5021 }
5022 }
5023
5024 rules->list = &rs->rules;
5025 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5026 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5027 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5028 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5029 rules->list = NULL;
5030 goto error;
5031 }
5032
5033 out:
5034 free(errmsg);
5035 return err_code;
5036
5037 error:
5038 free_tcpcheck_ruleset(rs);
5039 free_tcpcheck(chk, 0);
5040 err_code |= ERR_ALERT | ERR_FATAL;
5041 goto out;
5042}
5043
5044/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005045int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005046 const char *file, int line)
5047{
5048 struct tcpcheck_ruleset *rs = NULL;
5049 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5050 int err_code = 0;
5051
5052 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5053 err_code |= ERR_WARN;
5054
5055 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5056 goto out;
5057
5058 curpx->options2 &= ~PR_O2_CHK_ANY;
5059 curpx->options2 |= PR_O2_TCPCHK_CHK;
5060
5061 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5062 /* If a tcp-check rulesset is already set, do nothing */
5063 if (rules->list)
5064 goto out;
5065
5066 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5067 * get it.
5068 */
5069 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5070 goto curpx_ruleset;
5071
5072 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5073 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5074 rs = find_tcpcheck_ruleset(b_orig(&trash));
5075 if (rs)
5076 goto ruleset_found;
5077 }
5078
5079 curpx_ruleset:
5080 /* Deduce the ruleset name from the proxy info */
5081 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5082 ((curpx == defpx) ? "defaults" : curpx->id),
5083 curpx->conf.file, curpx->conf.line);
5084
5085 rs = find_tcpcheck_ruleset(b_orig(&trash));
5086 if (rs == NULL) {
5087 rs = create_tcpcheck_ruleset(b_orig(&trash));
5088 if (rs == NULL) {
5089 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5090 goto error;
5091 }
5092 }
5093
5094 ruleset_found:
5095 free_tcpcheck_vars(&rules->preset_vars);
5096 rules->list = &rs->rules;
5097 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5098 rules->flags |= TCPCHK_RULES_TCP_CHK;
5099
5100 out:
5101 return err_code;
5102
5103 error:
5104 err_code |= ERR_ALERT | ERR_FATAL;
5105 goto out;
5106}
5107
Willy Tarreau51cd5952020-06-05 12:25:38 +02005108static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005109 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005110 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5111 { 0, NULL, NULL },
5112}};
5113
5114REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5115REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5116REGISTER_POST_DEINIT(deinit_tcpchecks);
5117INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);