blob: 4e594a9f795dd8fc7356d8bd652a3a29d426927d [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);
Amaury Denoyelle02742862021-06-18 11:11:36 +0200940 /* This is safe to call server_parse_maxconn_change_request
941 * because the server lock is held during the check.
942 */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200943 msg = server_parse_maxconn_change_request(check->server, cs);
944 if (!wrn || !*wrn)
945 wrn = msg;
946 }
947
948 /* and finally health status */
949 if (hs) {
950 /* We'll report some of the warnings and errors we have
951 * here. Down reports are critical, we leave them untouched.
952 * Lack of report, or report of 'UP' leaves the room for
953 * ERR first, then WARN.
954 */
955 const char *msg = cmd;
956 struct buffer *t;
957
958 if (!*msg || status == HCHK_STATUS_L7OKD) {
959 if (err && *err)
960 msg = err;
961 else if (wrn && *wrn)
962 msg = wrn;
963 }
964
965 t = get_trash_chunk();
966 chunk_printf(t, "via agent : %s%s%s%s",
967 hs, *msg ? " (" : "",
968 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +0200969 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200970 set_server_check_status(check, status, t->area);
971 }
972 else if (err && *err) {
973 /* No status change but we'd like to report something odd.
974 * Just report the current state and copy the message.
975 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200976 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200977 chunk_printf(&trash, "agent reports an error : %s", err);
978 set_server_check_status(check, status/*check->status*/, trash.area);
979 }
980 else if (wrn && *wrn) {
981 /* No status change but we'd like to report something odd.
982 * Just report the current state and copy the message.
983 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200984 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200985 chunk_printf(&trash, "agent warns : %s", wrn);
986 set_server_check_status(check, status/*check->status*/, trash.area);
987 }
Christopher Faulet147b8c92021-04-10 09:00:38 +0200988 else {
989 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200990 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200991 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200992
993 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200994 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200995 return ret;
996
997 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200998 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200999 ret = TCPCHK_EVAL_WAIT;
1000 goto out;
1001}
1002
1003/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1004 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1005 * TCPCHK_EVAL_STOP if an error occurred.
1006 */
1007enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1008{
1009 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1010 struct tcpcheck_connect *connect = &rule->connect;
1011 struct proxy *proxy = check->proxy;
1012 struct server *s = check->server;
1013 struct task *t = check->task;
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001014 struct conn_stream *cs = check->cs;
1015 struct connection *conn = cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001016 struct protocol *proto;
1017 struct xprt_ops *xprt;
1018 struct tcpcheck_rule *next;
1019 int status, port;
1020
Christopher Faulet147b8c92021-04-10 09:00:38 +02001021 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1022
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001023 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1024
1025 /* current connection already created, check if it is established or not */
1026 if (conn) {
1027 if (conn->flags & CO_FL_WAIT_XPRT) {
1028 /* We are still waiting for the connection establishment */
1029 if (next && next->action == TCPCHK_ACT_SEND) {
1030 if (!(check->wait_list.events & SUB_RETRY_SEND))
1031 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1032 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001033 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001034 }
1035 else
1036 ret = tcpcheck_eval_recv(check, rule);
1037 }
1038 goto out;
1039 }
1040
1041 /* Note: here check->cs = cs = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001042
Christopher Fauletb381a502020-11-25 13:47:00 +01001043 /* Always release input and output buffer when a new connect is evaluated */
1044 check_release_buf(check, &check->bi);
1045 check_release_buf(check, &check->bo);
1046
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001047 /* No connection, prepare a new one */
Christopher Faulet236c93b2020-07-02 09:19:54 +02001048 cs = cs_new(NULL, (s ? &s->obj_type : &proxy->obj_type));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001049 if (!cs) {
1050 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1051 tcpcheck_get_step_id(check, rule));
1052 if (rule->comment)
1053 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1054 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1055 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001056 TRACE_ERROR("conn-stream allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001057 goto out;
1058 }
1059
Willy Tarreau51cd5952020-06-05 12:25:38 +02001060 tasklet_set_tid(check->wait_list.tasklet, tid);
1061
1062 check->cs = cs;
1063 conn = cs->conn;
1064 conn_set_owner(conn, check->sess, NULL);
1065
1066 /* Maybe there were an older connection we were waiting on */
1067 check->wait_list.events = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001068
1069 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001070 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001071 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001072 status = SF_ERR_RESOURCE;
1073 goto fail_check;
1074 }
1075
1076 /* connect to the connect rule addr if specified, otherwise the check
1077 * addr if specified on the server. otherwise, use the server addr (it
1078 * MUST exist at this step).
1079 */
1080 *conn->dst = (is_addr(&connect->addr)
1081 ? connect->addr
1082 : (is_addr(&check->addr) ? check->addr : s->addr));
1083 proto = protocol_by_family(conn->dst->ss_family);
1084
1085 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001086 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001087 port = connect->port;
1088 if (!port && connect->port_expr) {
1089 struct sample *smp;
1090
1091 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1092 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1093 connect->port_expr, SMP_T_SINT);
1094 if (smp)
1095 port = smp->data.u.sint;
1096 }
1097 if (!port && is_inet_addr(&connect->addr))
1098 port = get_host_port(&connect->addr);
1099 if (!port && check->port)
1100 port = check->port;
1101 if (!port && is_inet_addr(&check->addr))
1102 port = get_host_port(&check->addr);
1103 if (!port) {
1104 /* The server MUST exist here */
1105 port = s->svc_port;
1106 }
1107 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001108 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001109
1110 xprt = ((connect->options & TCPCHK_OPT_SSL)
1111 ? xprt_get(XPRT_SSL)
1112 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1113
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001114 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001115 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001116 status = SF_ERR_RESOURCE;
1117 goto fail_check;
1118 }
1119
Willy Tarreau51cd5952020-06-05 12:25:38 +02001120 cs_attach(cs, check, &check_conn_cb);
1121
Christopher Fauletf7177272020-10-02 13:41:55 +02001122 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1123 conn->send_proxy_ofs = 1;
1124 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001125 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001126 }
1127 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1128 conn->send_proxy_ofs = 1;
1129 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001130 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001131 }
1132
1133 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1134 conn->send_proxy_ofs = 1;
1135 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001136 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001137 }
1138 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1139 conn->send_proxy_ofs = 1;
1140 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001141 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001142 }
1143
Willy Tarreau51cd5952020-06-05 12:25:38 +02001144 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001145 if (proto && proto->connect) {
1146 int flags = 0;
1147
1148 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1149 flags |= CONNECT_HAS_DATA;
1150 if (!next || next->action != TCPCHK_ACT_EXPECT)
1151 flags |= CONNECT_DELACK_ALWAYS;
1152 status = proto->connect(conn, flags);
1153 }
1154
1155 if (status != SF_ERR_NONE)
1156 goto fail_check;
1157
Christopher Faulet21ddc742020-07-01 15:26:14 +02001158 conn_set_private(conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001159 conn->ctx = cs;
1160
Willy Tarreau51cd5952020-06-05 12:25:38 +02001161#ifdef USE_OPENSSL
1162 if (connect->sni)
1163 ssl_sock_set_servername(conn, connect->sni);
1164 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1165 ssl_sock_set_servername(conn, s->check.sni);
1166
1167 if (connect->alpn)
1168 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1169 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1170 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1171#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001172
1173 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1174 /* Some servers don't like reset on close */
Willy Tarreaub41a6e92021-04-06 17:49:19 +02001175 HA_ATOMIC_AND(&fdtab[cs->conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001176 }
1177
1178 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1179 if (xprt_add_hs(conn) < 0)
1180 status = SF_ERR_RESOURCE;
1181 }
1182
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001183 if (conn_xprt_start(conn) < 0) {
1184 status = SF_ERR_RESOURCE;
1185 goto fail_check;
1186 }
1187
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001188 /* The mux may be initialized now if there isn't server attached to the
1189 * check (email alerts) or if there is a mux proto specified or if there
1190 * is no alpn.
1191 */
1192 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1193 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1194 const struct mux_ops *mux_ops;
1195
Christopher Faulet147b8c92021-04-10 09:00:38 +02001196 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001197 if (connect->mux_proto)
1198 mux_ops = connect->mux_proto->mux;
1199 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1200 mux_ops = check->mux_proto->mux;
1201 else {
1202 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1203 ? PROTO_MODE_HTTP
1204 : PROTO_MODE_TCP);
1205
1206 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1207 }
1208 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001209 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001210 status = SF_ERR_INTERNAL;
1211 goto fail_check;
1212 }
1213 }
1214
Willy Tarreau51cd5952020-06-05 12:25:38 +02001215 fail_check:
1216 /* It can return one of :
1217 * - SF_ERR_NONE if everything's OK
1218 * - SF_ERR_SRVTO if there are no more servers
1219 * - SF_ERR_SRVCL if the connection was refused by the server
1220 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1221 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1222 * - SF_ERR_INTERNAL for any other purely internal errors
1223 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1224 * Note that we try to prevent the network stack from sending the ACK during the
1225 * connect() when a pure TCP check is used (without PROXY protocol).
1226 */
1227 switch (status) {
1228 case SF_ERR_NONE:
1229 /* we allow up to min(inter, timeout.connect) for a connection
1230 * to establish but only when timeout.check is set as it may be
1231 * to short for a full check otherwise
1232 */
1233 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1234
1235 if (proxy->timeout.check && proxy->timeout.connect) {
1236 int t_con = tick_add(now_ms, proxy->timeout.connect);
1237 t->expire = tick_first(t->expire, t_con);
1238 }
1239 break;
1240 case SF_ERR_SRVTO: /* ETIMEDOUT */
1241 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1242 case SF_ERR_PRXCOND:
1243 case SF_ERR_RESOURCE:
1244 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001245 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 +02001246 chk_report_conn_err(check, errno, 0);
1247 ret = TCPCHK_EVAL_STOP;
1248 goto out;
1249 }
1250
1251 /* don't do anything until the connection is established */
1252 if (conn->flags & CO_FL_WAIT_XPRT) {
1253 if (conn->mux) {
1254 if (next && next->action == TCPCHK_ACT_SEND)
1255 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1256 else
1257 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
1258 }
1259 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001260 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001261 goto out;
1262 }
1263
1264 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001265 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001266 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001267 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001268 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001269
1270 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1271 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1272
Christopher Faulet147b8c92021-04-10 09:00:38 +02001273 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001274 return ret;
1275}
1276
1277/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1278 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1279 * TCPCHK_EVAL_STOP if an error occurred.
1280 */
1281enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1282{
1283 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1284 struct tcpcheck_send *send = &rule->send;
1285 struct conn_stream *cs = check->cs;
1286 struct connection *conn = cs_conn(cs);
1287 struct buffer *tmp = NULL;
1288 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001289 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001290
Christopher Faulet147b8c92021-04-10 09:00:38 +02001291 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1292
Christopher Fauletb381a502020-11-25 13:47:00 +01001293 if (check->state & CHK_ST_OUT_ALLOC) {
1294 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001295 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 +01001296 goto out;
1297 }
1298
1299 if (!check_get_buf(check, &check->bo)) {
1300 check->state |= CHK_ST_OUT_ALLOC;
1301 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001302 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 +01001303 goto out;
1304 }
1305
Christopher Faulet39066c22020-11-25 13:34:51 +01001306 /* Data already pending in the output buffer, send them now */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001307 if (b_data(&check->bo)) {
1308 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 +01001309 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001310 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001311
Christopher Fauletb381a502020-11-25 13:47:00 +01001312 /* Always release input buffer when a new send is evaluated */
1313 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001314
1315 switch (send->type) {
1316 case TCPCHK_SEND_STRING:
1317 case TCPCHK_SEND_BINARY:
1318 if (istlen(send->data) >= b_size(&check->bo)) {
1319 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1320 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1321 tcpcheck_get_step_id(check, rule));
1322 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1323 ret = TCPCHK_EVAL_STOP;
1324 goto out;
1325 }
1326 b_putist(&check->bo, send->data);
1327 break;
1328 case TCPCHK_SEND_STRING_LF:
1329 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1330 if (!b_data(&check->bo))
1331 goto out;
1332 break;
1333 case TCPCHK_SEND_BINARY_LF: {
1334 int len = b_size(&check->bo);
1335
1336 tmp = alloc_trash_chunk();
1337 if (!tmp)
1338 goto error_lf;
1339 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1340 if (!b_data(tmp))
1341 goto out;
1342 tmp->area[tmp->data] = '\0';
1343 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1344 goto error_lf;
1345 check->bo.data = len;
1346 break;
1347 }
1348 case TCPCHK_SEND_HTTP: {
1349 struct htx_sl *sl;
1350 struct ist meth, uri, vsn, clen, body;
1351 unsigned int slflags = 0;
1352
1353 tmp = alloc_trash_chunk();
1354 if (!tmp)
1355 goto error_htx;
1356
1357 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1358 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1359 : http_known_methods[send->http.meth.meth]);
1360 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1361 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1362 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1363 }
1364 else
1365 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1366 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1367
1368 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1369 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1370 slflags |= HTX_SL_F_VER_11;
1371 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1372 if (!isttest(send->http.body))
1373 slflags |= HTX_SL_F_BODYLESS;
1374
1375 htx = htx_from_buf(&check->bo);
1376 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1377 if (!sl)
1378 goto error_htx;
1379 sl->info.req.meth = send->http.meth.meth;
1380 if (!http_update_host(htx, sl, uri))
1381 goto error_htx;
1382
1383 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1384 struct tcpcheck_http_hdr *hdr;
1385 struct ist hdr_value;
1386
1387 list_for_each_entry(hdr, &send->http.hdrs, list) {
1388 chunk_reset(tmp);
1389 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1390 if (!b_data(tmp))
1391 continue;
1392 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1393 if (!htx_add_header(htx, hdr->name, hdr_value))
1394 goto error_htx;
1395 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1396 if (!http_update_authority(htx, sl, hdr_value))
1397 goto error_htx;
1398 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001399 if (isteqi(hdr->name, ist("connection")))
1400 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001401 }
1402
1403 }
1404 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1405 chunk_reset(tmp);
1406 httpchk_build_status_header(check->server, tmp);
1407 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1408 goto error_htx;
1409 }
1410
1411
1412 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1413 chunk_reset(tmp);
1414 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1415 body = ist2(b_orig(tmp), b_data(tmp));
1416 }
1417 else
1418 body = send->http.body;
1419 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1420
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001421 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001422 !htx_add_header(htx, ist("Content-length"), clen))
1423 goto error_htx;
1424
1425
1426 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001427 (istlen(body) && !htx_add_data_atonce(htx, body)))
1428 goto error_htx;
1429
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001430 /* no more data are expected */
1431 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001432 htx_to_buf(htx, &check->bo);
1433 break;
1434 }
1435 case TCPCHK_SEND_UNDEF:
1436 /* Should never happen. */
1437 ret = TCPCHK_EVAL_STOP;
1438 goto out;
1439 };
1440
Christopher Faulet39066c22020-11-25 13:34:51 +01001441 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001442 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001443 if (conn->mux->snd_buf(cs, &check->bo,
1444 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1445 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1446 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001447 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 +02001448 goto out;
1449 }
1450 }
1451 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
1452 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1453 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001454 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001455 goto out;
1456 }
1457
1458 out:
1459 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001460 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1461 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001462
1463 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001464 return ret;
1465
1466 error_htx:
1467 if (htx) {
1468 htx_reset(htx);
1469 htx_to_buf(htx, &check->bo);
1470 }
1471 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1472 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001473 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 +02001474 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1475 ret = TCPCHK_EVAL_STOP;
1476 goto out;
1477
1478 error_lf:
1479 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1480 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001481 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 +02001482 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1483 ret = TCPCHK_EVAL_STOP;
1484 goto out;
1485
1486}
1487
1488/* Try to receive data before evaluating a tcp-check expect rule. Returns
1489 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1490 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1491 * TCPCHK_EVAL_STOP if an error occurred.
1492 */
1493enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1494{
1495 struct conn_stream *cs = check->cs;
1496 struct connection *conn = cs_conn(cs);
1497 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1498 size_t max, read, cur_read = 0;
1499 int is_empty;
1500 int read_poll = MAX_READ_POLL_LOOPS;
1501
Christopher Faulet147b8c92021-04-10 09:00:38 +02001502 TRACE_ENTER(CHK_EV_RX_DATA, check);
1503
1504 if (check->wait_list.events & SUB_RETRY_RECV) {
1505 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001506 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001507 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001508
1509 if (cs->flags & CS_FL_EOS)
1510 goto end_recv;
1511
Christopher Faulet147b8c92021-04-10 09:00:38 +02001512 if (check->state & CHK_ST_IN_ALLOC) {
1513 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001514 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001515 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001516
1517 if (!check_get_buf(check, &check->bi)) {
1518 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001519 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001520 goto wait_more_data;
1521 }
1522
Willy Tarreau51cd5952020-06-05 12:25:38 +02001523 /* errors on the connection and the conn-stream were already checked */
1524
1525 /* prepare to detect if the mux needs more room */
1526 cs->flags &= ~CS_FL_WANT_ROOM;
1527
1528 while ((cs->flags & CS_FL_RCV_MORE) ||
1529 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1530 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1531 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1532 cur_read += read;
1533 if (!read ||
1534 (cs->flags & CS_FL_WANT_ROOM) ||
1535 (--read_poll <= 0) ||
1536 (read < max && read >= global.tune.recv_enough))
1537 break;
1538 }
1539
1540 end_recv:
1541 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1542 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1543 /* Report network errors only if we got no other data. Otherwise
1544 * we'll let the upper layers decide whether the response is OK
1545 * or not. It is very common that an RST sent by the server is
1546 * reported as an error just after the last data chunk.
1547 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001548 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001549 goto stop;
1550 }
1551 if (!cur_read) {
1552 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1553 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001554 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001555 goto wait_more_data;
1556 }
1557 if (is_empty) {
1558 int status;
1559
1560 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1561 tcpcheck_get_step_id(check, rule));
1562 if (rule->comment)
1563 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1564
Christopher Faulet147b8c92021-04-10 09:00:38 +02001565 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001566 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1567 set_server_check_status(check, status, trash.area);
1568 goto stop;
1569 }
1570 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001571 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001572
1573 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001574 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1575 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001576
1577 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001578 return ret;
1579
1580 stop:
1581 ret = TCPCHK_EVAL_STOP;
1582 goto out;
1583
1584 wait_more_data:
1585 ret = TCPCHK_EVAL_WAIT;
1586 goto out;
1587}
1588
1589/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1590 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1591 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1592 * error occurred.
1593 */
1594enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1595{
1596 struct htx *htx = htxbuf(&check->bi);
1597 struct htx_sl *sl;
1598 struct htx_blk *blk;
1599 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1600 struct tcpcheck_expect *expect = &rule->expect;
1601 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1602 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1603 struct ist desc = IST_NULL;
1604 int i, match, inverse;
1605
Christopher Faulet147b8c92021-04-10 09:00:38 +02001606 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1607
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001608 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001609
1610 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001611 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001612 status = HCHK_STATUS_L7RSP;
1613 goto error;
1614 }
1615
1616 if (htx_is_empty(htx)) {
1617 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001618 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001619 status = HCHK_STATUS_L7RSP;
1620 goto error;
1621 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001622 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001623 goto wait_more_data;
1624 }
1625
1626 sl = http_get_stline(htx);
1627 check->code = sl->info.res.status;
1628
1629 if (check->server &&
1630 (check->server->proxy->options & PR_O_DISABLE404) &&
1631 (check->server->next_state != SRV_ST_STOPPED) &&
1632 (check->code == 404)) {
1633 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001634 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001635 goto out;
1636 }
1637
1638 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1639 /* Make GCC happy ; initialize match to a failure state. */
1640 match = inverse;
1641 status = expect->err_status;
1642
1643 switch (expect->type) {
1644 case TCPCHK_EXPECT_HTTP_STATUS:
1645 match = 0;
1646 for (i = 0; i < expect->codes.num; i++) {
1647 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1648 sl->info.res.status <= expect->codes.codes[i][1]) {
1649 match = 1;
1650 break;
1651 }
1652 }
1653
1654 /* Set status and description in case of error */
1655 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1656 if (LIST_ISEMPTY(&expect->onerror_fmt))
1657 desc = htx_sl_res_reason(sl);
1658 break;
1659 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1660 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1661
1662 /* Set status and description in case of error */
1663 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1664 if (LIST_ISEMPTY(&expect->onerror_fmt))
1665 desc = htx_sl_res_reason(sl);
1666 break;
1667
1668 case TCPCHK_EXPECT_HTTP_HEADER: {
1669 struct http_hdr_ctx ctx;
1670 struct ist npat, vpat, value;
1671 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1672
1673 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1674 nbuf = alloc_trash_chunk();
1675 if (!nbuf) {
1676 status = HCHK_STATUS_L7RSP;
1677 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001678 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001679 goto error;
1680 }
1681 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1682 if (!b_data(nbuf)) {
1683 status = HCHK_STATUS_L7RSP;
1684 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001685 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001686 goto error;
1687 }
1688 npat = ist2(b_orig(nbuf), b_data(nbuf));
1689 }
1690 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1691 npat = expect->hdr.name;
1692
1693 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1694 vbuf = alloc_trash_chunk();
1695 if (!vbuf) {
1696 status = HCHK_STATUS_L7RSP;
1697 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001698 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001699 goto error;
1700 }
1701 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1702 if (!b_data(vbuf)) {
1703 status = HCHK_STATUS_L7RSP;
1704 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001705 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001706 goto error;
1707 }
1708 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1709 }
1710 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1711 vpat = expect->hdr.value;
1712
1713 match = 0;
1714 ctx.blk = NULL;
1715 while (1) {
1716 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1717 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1718 if (!http_find_str_header(htx, npat, &ctx, full))
1719 goto end_of_match;
1720 break;
1721 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1722 if (!http_find_pfx_header(htx, npat, &ctx, full))
1723 goto end_of_match;
1724 break;
1725 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1726 if (!http_find_sfx_header(htx, npat, &ctx, full))
1727 goto end_of_match;
1728 break;
1729 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1730 if (!http_find_sub_header(htx, npat, &ctx, full))
1731 goto end_of_match;
1732 break;
1733 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1734 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1735 goto end_of_match;
1736 break;
1737 default:
1738 /* should never happen */
1739 goto end_of_match;
1740 }
1741
1742 /* A header has matched the name pattern, let's test its
1743 * value now (always defined from there). If there is no
1744 * value pattern, it is a good match.
1745 */
1746
1747 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1748 match = 1;
1749 goto end_of_match;
1750 }
1751
1752 value = ctx.value;
1753 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1754 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1755 if (isteq(value, vpat)) {
1756 match = 1;
1757 goto end_of_match;
1758 }
1759 break;
1760 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1761 if (istlen(value) < istlen(vpat))
1762 break;
1763 value = ist2(istptr(value), istlen(vpat));
1764 if (isteq(value, vpat)) {
1765 match = 1;
1766 goto end_of_match;
1767 }
1768 break;
1769 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1770 if (istlen(value) < istlen(vpat))
1771 break;
1772 value = ist2(istptr(value) + istlen(value) - istlen(vpat), istlen(vpat));
1773 if (isteq(value, vpat)) {
1774 match = 1;
1775 goto end_of_match;
1776 }
1777 break;
1778 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1779 if (isttest(istist(value, vpat))) {
1780 match = 1;
1781 goto end_of_match;
1782 }
1783 break;
1784 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1785 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1786 match = 1;
1787 goto end_of_match;
1788 }
1789 break;
1790 }
1791 }
1792
1793 end_of_match:
1794 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1795 if (LIST_ISEMPTY(&expect->onerror_fmt))
1796 desc = htx_sl_res_reason(sl);
1797 break;
1798 }
1799
1800 case TCPCHK_EXPECT_HTTP_BODY:
1801 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1802 case TCPCHK_EXPECT_HTTP_BODY_LF:
1803 match = 0;
1804 chunk_reset(&trash);
1805 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1806 enum htx_blk_type type = htx_get_blk_type(blk);
1807
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001808 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001809 break;
1810 if (type == HTX_BLK_DATA) {
1811 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1812 break;
1813 }
1814 }
1815
1816 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001817 if (!last_read) {
1818 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001819 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001820 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001821 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1822 if (LIST_ISEMPTY(&expect->onerror_fmt))
1823 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001824 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001825 goto error;
1826 }
1827
1828 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1829 tmp = alloc_trash_chunk();
1830 if (!tmp) {
1831 status = HCHK_STATUS_L7RSP;
1832 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001833 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001834 goto error;
1835 }
1836 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1837 if (!b_data(tmp)) {
1838 status = HCHK_STATUS_L7RSP;
1839 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001840 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001841 goto error;
1842 }
1843 }
1844
1845 if (!last_read &&
1846 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1847 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1848 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1849 ret = TCPCHK_EVAL_WAIT;
1850 goto out;
1851 }
1852
1853 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1854 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1855 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1856 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1857 else
1858 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1859
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001860 /* Wait for more data on mismatch only if no minimum is defined (-1),
1861 * otherwise the absence of match is already conclusive.
1862 */
1863 if (!match && !last_read && (expect->min_recv == -1)) {
1864 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001865 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001866 goto out;
1867 }
1868
Willy Tarreau51cd5952020-06-05 12:25:38 +02001869 /* Set status and description in case of error */
1870 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1871 if (LIST_ISEMPTY(&expect->onerror_fmt))
1872 desc = (inverse
1873 ? ist("HTTP check matched unwanted content")
1874 : ist("HTTP content check did not match"));
1875 break;
1876
1877
1878 default:
1879 /* should never happen */
1880 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1881 goto error;
1882 }
1883
Christopher Faulet147b8c92021-04-10 09:00:38 +02001884 if (!(match ^ inverse)) {
1885 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001886 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001887 }
1888
1889 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001890
1891 out:
1892 free_trash_chunk(tmp);
1893 free_trash_chunk(nbuf);
1894 free_trash_chunk(vbuf);
1895 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001896 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001897 return ret;
1898
1899 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001900 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001901 ret = TCPCHK_EVAL_STOP;
1902 msg = alloc_trash_chunk();
1903 if (msg)
1904 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1905 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1906 goto out;
1907
1908 wait_more_data:
1909 ret = TCPCHK_EVAL_WAIT;
1910 goto out;
1911}
1912
1913/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1914 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1915 * if an error occurred.
1916 */
1917enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1918{
1919 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1920 struct tcpcheck_expect *expect = &rule->expect;
1921 struct buffer *msg = NULL, *tmp = NULL;
1922 struct ist desc = IST_NULL;
1923 enum healthcheck_status status;
1924 int match, inverse;
1925
Christopher Faulet147b8c92021-04-10 09:00:38 +02001926 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1927
Willy Tarreau51cd5952020-06-05 12:25:38 +02001928 last_read |= b_full(&check->bi);
1929
1930 /* The current expect might need more data than the previous one, check again
1931 * that the minimum amount data required to match is respected.
1932 */
1933 if (!last_read) {
1934 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1935 (b_data(&check->bi) < istlen(expect->data))) {
1936 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001937 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001938 goto out;
1939 }
1940 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1941 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001942 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001943 goto out;
1944 }
1945 }
1946
1947 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1948 /* Make GCC happy ; initialize match to a failure state. */
1949 match = inverse;
1950 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1951
1952 switch (expect->type) {
1953 case TCPCHK_EXPECT_STRING:
1954 case TCPCHK_EXPECT_BINARY:
1955 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
1956 break;
1957 case TCPCHK_EXPECT_STRING_REGEX:
1958 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
1959 break;
1960
1961 case TCPCHK_EXPECT_BINARY_REGEX:
1962 chunk_reset(&trash);
1963 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
1964 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
1965 break;
1966
1967 case TCPCHK_EXPECT_STRING_LF:
1968 case TCPCHK_EXPECT_BINARY_LF:
1969 match = 0;
1970 tmp = alloc_trash_chunk();
1971 if (!tmp) {
1972 status = HCHK_STATUS_L7RSP;
1973 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001974 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001975 goto error;
1976 }
1977 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1978 if (!b_data(tmp)) {
1979 status = HCHK_STATUS_L7RSP;
1980 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001981 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001982 goto error;
1983 }
1984 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
1985 int len = tmp->data;
1986 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
1987 status = HCHK_STATUS_L7RSP;
1988 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001989 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001990 goto error;
1991 }
1992 tmp->data = len;
1993 }
1994 if (b_data(&check->bi) < tmp->data) {
1995 if (!last_read) {
1996 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001997 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001998 goto out;
1999 }
2000 break;
2001 }
2002 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2003 break;
2004
2005 case TCPCHK_EXPECT_CUSTOM:
2006 if (expect->custom)
2007 ret = expect->custom(check, rule, last_read);
2008 goto out;
2009 default:
2010 /* Should never happen. */
2011 ret = TCPCHK_EVAL_STOP;
2012 goto out;
2013 }
2014
2015
2016 /* Wait for more data on mismatch only if no minimum is defined (-1),
2017 * otherwise the absence of match is already conclusive.
2018 */
2019 if (!match && !last_read && (expect->min_recv == -1)) {
2020 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002021 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002022 goto out;
2023 }
2024
2025 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002026 if (match ^ inverse) {
2027 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002028 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002029 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002030
2031 error:
2032 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002033 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002034 ret = TCPCHK_EVAL_STOP;
2035 msg = alloc_trash_chunk();
2036 if (msg)
2037 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2038 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2039 free_trash_chunk(msg);
2040
2041 out:
2042 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002043 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002044 return ret;
2045}
2046
2047/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2048 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2049 * waits.
2050 */
2051enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2052{
2053 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2054 struct act_rule *act_rule;
2055 enum act_return act_ret;
2056
2057 act_rule =rule->action_kw.rule;
2058 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2059 if (act_ret != ACT_RET_CONT) {
2060 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2061 tcpcheck_get_step_id(check, rule));
2062 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2063 ret = TCPCHK_EVAL_STOP;
2064 }
2065
2066 return ret;
2067}
2068
2069/* Executes a tcp-check ruleset. Note that this is called both from the
2070 * connection's wake() callback and from the check scheduling task. It returns
2071 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2072 * presenting the risk of an fd replacement.
2073 *
2074 * Please do NOT place any return statement in this function and only leave
2075 * via the out_end_tcpcheck label after setting retcode.
2076 */
2077int tcpcheck_main(struct check *check)
2078{
2079 struct tcpcheck_rule *rule;
2080 struct conn_stream *cs = check->cs;
2081 struct connection *conn = cs_conn(cs);
2082 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002083 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002084 enum tcpcheck_eval_ret eval_ret;
2085
2086 /* here, we know that the check is complete or that it failed */
2087 if (check->result != CHK_RES_UNKNOWN)
2088 goto out;
2089
Christopher Faulet147b8c92021-04-10 09:00:38 +02002090 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2091
Willy Tarreau51cd5952020-06-05 12:25:38 +02002092 /* Note: the conn-stream and the connection may only be undefined before
2093 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002094 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002095 */
2096
2097 /* 1- check for connection error, if any */
2098 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2099 goto out_end_tcpcheck;
2100
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002101 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002102 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002103 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002104 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002105 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2106 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002107
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002108 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002109 * tcp-check variables */
2110 else {
2111 struct tcpcheck_var *var;
2112
2113 /* First evaluation, create a session */
2114 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2115 if (!check->sess) {
2116 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002117 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002118 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2119 goto out_end_tcpcheck;
2120 }
2121 vars_init(&check->vars, SCOPE_CHECK);
2122 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2123
2124 /* Preset tcp-check variables */
2125 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2126 struct sample smp;
2127
2128 memset(&smp, 0, sizeof(smp));
2129 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2130 smp.data = var->data;
2131 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2132 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002133 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002134 }
2135
2136 /* Now evaluate the tcp-check rules */
2137
2138 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2139 check->code = 0;
2140 switch (rule->action) {
2141 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002142 /* Not the first connection, release it first */
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002143 if (cs && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002144 check->state |= CHK_ST_CLOSE_CONN;
2145 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002146 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002147
2148 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002149
2150 /* We are still waiting the connection gets closed */
2151 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002152 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002153 eval_ret = TCPCHK_EVAL_WAIT;
2154 break;
2155 }
2156
Christopher Faulet147b8c92021-04-10 09:00:38 +02002157 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002158 eval_ret = tcpcheck_eval_connect(check, rule);
2159
2160 /* Refresh conn-stream and connection */
2161 cs = check->cs;
2162 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002163 last_read = 0;
2164 must_read = ((cs && IS_HTX_CS(cs)) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002165 break;
2166 case TCPCHK_ACT_SEND:
2167 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002168 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002169 eval_ret = tcpcheck_eval_send(check, rule);
2170 must_read = 1;
2171 break;
2172 case TCPCHK_ACT_EXPECT:
2173 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002174 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002175 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002176 eval_ret = tcpcheck_eval_recv(check, rule);
2177 if (eval_ret == TCPCHK_EVAL_STOP)
2178 goto out_end_tcpcheck;
2179 else if (eval_ret == TCPCHK_EVAL_WAIT)
2180 goto out;
2181 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2182 must_read = 0;
2183 }
2184
2185 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2186 ? tcpcheck_eval_expect_http(check, rule, last_read)
2187 : tcpcheck_eval_expect(check, rule, last_read));
2188
2189 if (eval_ret == TCPCHK_EVAL_WAIT) {
2190 check->current_step = rule->expect.head;
2191 if (!(check->wait_list.events & SUB_RETRY_RECV))
2192 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2193 }
2194 break;
2195 case TCPCHK_ACT_ACTION_KW:
2196 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002197 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002198 eval_ret = tcpcheck_eval_action_kw(check, rule);
2199 break;
2200 default:
2201 /* Otherwise, just go to the next one and don't update
2202 * the current step
2203 */
2204 eval_ret = TCPCHK_EVAL_CONTINUE;
2205 break;
2206 }
2207
2208 switch (eval_ret) {
2209 case TCPCHK_EVAL_CONTINUE:
2210 break;
2211 case TCPCHK_EVAL_WAIT:
2212 goto out;
2213 case TCPCHK_EVAL_STOP:
2214 goto out_end_tcpcheck;
2215 }
2216 }
2217
2218 /* All rules was evaluated */
2219 if (check->current_step) {
2220 rule = check->current_step;
2221
Christopher Faulet147b8c92021-04-10 09:00:38 +02002222 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2223
Willy Tarreau51cd5952020-06-05 12:25:38 +02002224 if (rule->action == TCPCHK_ACT_EXPECT) {
2225 struct buffer *msg;
2226 enum healthcheck_status status;
2227
2228 if (check->server &&
2229 (check->server->proxy->options & PR_O_DISABLE404) &&
2230 (check->server->next_state != SRV_ST_STOPPED) &&
2231 (check->code == 404)) {
2232 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002233 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002234 goto out_end_tcpcheck;
2235 }
2236
2237 msg = alloc_trash_chunk();
2238 if (msg)
2239 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2240 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2241 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2242 free_trash_chunk(msg);
2243 }
2244 else if (rule->action == TCPCHK_ACT_CONNECT) {
2245 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2246 enum healthcheck_status status = HCHK_STATUS_L4OK;
2247#ifdef USE_OPENSSL
2248 if (ssl_sock_is_ssl(conn))
2249 status = HCHK_STATUS_L6OK;
2250#endif
2251 set_server_check_status(check, status, msg);
2252 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002253 else
2254 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002255 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002256 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002257 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002258 }
2259 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002260
2261 out_end_tcpcheck:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002262 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) {
2263 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002264 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002265 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002266
Christopher Fauletb381a502020-11-25 13:47:00 +01002267 /* the tcpcheck is finished, release in/out buffer now */
2268 check_release_buf(check, &check->bi);
2269 check_release_buf(check, &check->bo);
2270
Willy Tarreau51cd5952020-06-05 12:25:38 +02002271 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002272 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002273 return retcode;
2274}
2275
2276
2277/**************************************************************************/
2278/******************* Internals to parse tcp-check rules *******************/
2279/**************************************************************************/
2280struct action_kw_list tcp_check_keywords = {
2281 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2282};
2283
2284/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2285 * returned on error.
2286 */
2287struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2288 struct list *rules, struct action_kw *kw,
2289 const char *file, int line, char **errmsg)
2290{
2291 struct tcpcheck_rule *chk = NULL;
2292 struct act_rule *actrule = NULL;
2293
2294 actrule = calloc(1, sizeof(*actrule));
2295 if (!actrule) {
2296 memprintf(errmsg, "out of memory");
2297 goto error;
2298 }
2299 actrule->kw = kw;
2300 actrule->from = ACT_F_TCP_CHK;
2301
2302 cur_arg++;
2303 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2304 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2305 goto error;
2306 }
2307
2308 chk = calloc(1, sizeof(*chk));
2309 if (!chk) {
2310 memprintf(errmsg, "out of memory");
2311 goto error;
2312 }
2313 chk->action = TCPCHK_ACT_ACTION_KW;
2314 chk->action_kw.rule = actrule;
2315 return chk;
2316
2317 error:
2318 free(actrule);
2319 return NULL;
2320}
2321
2322/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2323 * returned on error.
2324 */
2325struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2326 const char *file, int line, char **errmsg)
2327{
2328 struct tcpcheck_rule *chk = NULL;
2329 struct sockaddr_storage *sk = NULL;
2330 char *comment = NULL, *sni = NULL, *alpn = NULL;
2331 struct sample_expr *port_expr = NULL;
2332 const struct mux_proto_list *mux_proto = NULL;
2333 unsigned short conn_opts = 0;
2334 long port = 0;
2335 int alpn_len = 0;
2336
2337 list_for_each_entry(chk, rules, list) {
2338 if (chk->action == TCPCHK_ACT_CONNECT)
2339 break;
2340 if (chk->action == TCPCHK_ACT_COMMENT ||
2341 chk->action == TCPCHK_ACT_ACTION_KW ||
2342 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2343 continue;
2344
2345 memprintf(errmsg, "first step MUST also be a 'connect', "
2346 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2347 "when there is a 'connect' step in the tcp-check ruleset");
2348 goto error;
2349 }
2350
2351 cur_arg++;
2352 while (*(args[cur_arg])) {
2353 if (strcmp(args[cur_arg], "default") == 0)
2354 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2355 else if (strcmp(args[cur_arg], "addr") == 0) {
2356 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002357
2358 if (!*(args[cur_arg+1])) {
2359 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2360 goto error;
2361 }
2362
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002363 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2364 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002365 if (!sk) {
2366 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2367 goto error;
2368 }
2369
Willy Tarreau51cd5952020-06-05 12:25:38 +02002370 cur_arg++;
2371 }
2372 else if (strcmp(args[cur_arg], "port") == 0) {
2373 const char *p, *end;
2374
2375 if (!*(args[cur_arg+1])) {
2376 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2377 goto error;
2378 }
2379 cur_arg++;
2380
2381 port = 0;
2382 release_sample_expr(port_expr);
2383 p = args[cur_arg]; end = p + strlen(p);
2384 port = read_uint(&p, end);
2385 if (p != end) {
2386 int idx = 0;
2387
2388 px->conf.args.ctx = ARGC_SRV;
2389 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
2390 file, line, errmsg, &px->conf.args, NULL);
2391
2392 if (!port_expr) {
2393 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2394 goto error;
2395 }
2396 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2397 memprintf(errmsg, "error detected while parsing port expression : "
2398 " fetch method '%s' extracts information from '%s', "
2399 "none of which is available here.\n",
2400 args[cur_arg], sample_src_names(port_expr->fetch->use));
2401 goto error;
2402 }
2403 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2404 }
2405 else if (port > 65535 || port < 1) {
2406 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2407 args[cur_arg]);
2408 goto error;
2409 }
2410 }
2411 else if (strcmp(args[cur_arg], "proto") == 0) {
2412 if (!*(args[cur_arg+1])) {
2413 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2414 goto error;
2415 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002416 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002417 if (!mux_proto) {
2418 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2419 goto error;
2420 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002421
2422 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2423 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2424 goto error;
2425 }
2426 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2427 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2428 goto error;
2429 }
2430
Willy Tarreau51cd5952020-06-05 12:25:38 +02002431 cur_arg++;
2432 }
2433 else if (strcmp(args[cur_arg], "comment") == 0) {
2434 if (!*(args[cur_arg+1])) {
2435 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2436 goto error;
2437 }
2438 cur_arg++;
2439 free(comment);
2440 comment = strdup(args[cur_arg]);
2441 if (!comment) {
2442 memprintf(errmsg, "out of memory");
2443 goto error;
2444 }
2445 }
2446 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2447 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2448 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2449 conn_opts |= TCPCHK_OPT_SOCKS4;
2450 else if (strcmp(args[cur_arg], "linger") == 0)
2451 conn_opts |= TCPCHK_OPT_LINGER;
2452#ifdef USE_OPENSSL
2453 else if (strcmp(args[cur_arg], "ssl") == 0) {
2454 px->options |= PR_O_TCPCHK_SSL;
2455 conn_opts |= TCPCHK_OPT_SSL;
2456 }
2457 else if (strcmp(args[cur_arg], "sni") == 0) {
2458 if (!*(args[cur_arg+1])) {
2459 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2460 goto error;
2461 }
2462 cur_arg++;
2463 free(sni);
2464 sni = strdup(args[cur_arg]);
2465 if (!sni) {
2466 memprintf(errmsg, "out of memory");
2467 goto error;
2468 }
2469 }
2470 else if (strcmp(args[cur_arg], "alpn") == 0) {
2471#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2472 free(alpn);
2473 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2474 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2475 goto error;
2476 }
2477 cur_arg++;
2478#else
2479 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2480 goto error;
2481#endif
2482 }
2483#endif /* USE_OPENSSL */
2484
2485 else {
2486 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2487#ifdef USE_OPENSSL
2488 ", 'ssl', 'sni', 'alpn'"
2489#endif /* USE_OPENSSL */
2490 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2491 args[cur_arg]);
2492 goto error;
2493 }
2494 cur_arg++;
2495 }
2496
2497 chk = calloc(1, sizeof(*chk));
2498 if (!chk) {
2499 memprintf(errmsg, "out of memory");
2500 goto error;
2501 }
2502 chk->action = TCPCHK_ACT_CONNECT;
2503 chk->comment = comment;
2504 chk->connect.port = port;
2505 chk->connect.options = conn_opts;
2506 chk->connect.sni = sni;
2507 chk->connect.alpn = alpn;
2508 chk->connect.alpn_len= alpn_len;
2509 chk->connect.port_expr= port_expr;
2510 chk->connect.mux_proto= mux_proto;
2511 if (sk)
2512 chk->connect.addr = *sk;
2513 return chk;
2514
2515 error:
2516 free(alpn);
2517 free(sni);
2518 free(comment);
2519 release_sample_expr(port_expr);
2520 return NULL;
2521}
2522
2523/* Parses and creates a tcp-check send rule. NULL is returned on error */
2524struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2525 const char *file, int line, char **errmsg)
2526{
2527 struct tcpcheck_rule *chk = NULL;
2528 char *comment = NULL, *data = NULL;
2529 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2530
2531 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2532 type = TCPCHK_SEND_BINARY_LF;
2533 else if (strcmp(args[cur_arg], "send-binary") == 0)
2534 type = TCPCHK_SEND_BINARY;
2535 else if (strcmp(args[cur_arg], "send-lf") == 0)
2536 type = TCPCHK_SEND_STRING_LF;
2537 else if (strcmp(args[cur_arg], "send") == 0)
2538 type = TCPCHK_SEND_STRING;
2539
2540 if (!*(args[cur_arg+1])) {
2541 memprintf(errmsg, "'%s' expects a %s as argument",
2542 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2543 goto error;
2544 }
2545
2546 data = args[cur_arg+1];
2547
2548 cur_arg += 2;
2549 while (*(args[cur_arg])) {
2550 if (strcmp(args[cur_arg], "comment") == 0) {
2551 if (!*(args[cur_arg+1])) {
2552 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2553 goto error;
2554 }
2555 cur_arg++;
2556 free(comment);
2557 comment = strdup(args[cur_arg]);
2558 if (!comment) {
2559 memprintf(errmsg, "out of memory");
2560 goto error;
2561 }
2562 }
2563 else {
2564 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2565 args[cur_arg]);
2566 goto error;
2567 }
2568 cur_arg++;
2569 }
2570
2571 chk = calloc(1, sizeof(*chk));
2572 if (!chk) {
2573 memprintf(errmsg, "out of memory");
2574 goto error;
2575 }
2576 chk->action = TCPCHK_ACT_SEND;
2577 chk->comment = comment;
2578 chk->send.type = type;
2579
2580 switch (chk->send.type) {
2581 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002582 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002583 if (!isttest(chk->send.data)) {
2584 memprintf(errmsg, "out of memory");
2585 goto error;
2586 }
2587 break;
2588 case TCPCHK_SEND_BINARY: {
2589 int len = chk->send.data.len;
2590 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2591 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2592 goto error;
2593 }
2594 chk->send.data.len = len;
2595 break;
2596 }
2597 case TCPCHK_SEND_STRING_LF:
2598 case TCPCHK_SEND_BINARY_LF:
2599 LIST_INIT(&chk->send.fmt);
2600 px->conf.args.ctx = ARGC_SRV;
2601 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2602 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2603 goto error;
2604 }
2605 break;
2606 case TCPCHK_SEND_HTTP:
2607 case TCPCHK_SEND_UNDEF:
2608 goto error;
2609 }
2610
2611 return chk;
2612
2613 error:
2614 free(chk);
2615 free(comment);
2616 return NULL;
2617}
2618
2619/* Parses and creates a http-check send rule. NULL is returned on error */
2620struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2621 const char *file, int line, char **errmsg)
2622{
2623 struct tcpcheck_rule *chk = NULL;
2624 struct tcpcheck_http_hdr *hdr = NULL;
2625 struct http_hdr hdrs[global.tune.max_http_hdr];
2626 char *meth = NULL, *uri = NULL, *vsn = NULL;
2627 char *body = NULL, *comment = NULL;
2628 unsigned int flags = 0;
2629 int i = 0, host_hdr = -1;
2630
2631 cur_arg++;
2632 while (*(args[cur_arg])) {
2633 if (strcmp(args[cur_arg], "meth") == 0) {
2634 if (!*(args[cur_arg+1])) {
2635 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2636 goto error;
2637 }
2638 cur_arg++;
2639 meth = args[cur_arg];
2640 }
2641 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2642 if (!*(args[cur_arg+1])) {
2643 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2644 goto error;
2645 }
2646 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2647 if (strcmp(args[cur_arg], "uri-lf") == 0)
2648 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2649 cur_arg++;
2650 uri = args[cur_arg];
2651 }
2652 else if (strcmp(args[cur_arg], "ver") == 0) {
2653 if (!*(args[cur_arg+1])) {
2654 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2655 goto error;
2656 }
2657 cur_arg++;
2658 vsn = args[cur_arg];
2659 }
2660 else if (strcmp(args[cur_arg], "hdr") == 0) {
2661 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2662 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2663 goto error;
2664 }
2665
2666 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2667 if (host_hdr >= 0) {
2668 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2669 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2670 goto error;
2671 }
2672 host_hdr = i;
2673 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002674 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002675 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2676 goto skip_hdr;
2677
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002678 hdrs[i].n = ist(args[cur_arg + 1]);
2679 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002680 i++;
2681 skip_hdr:
2682 cur_arg += 2;
2683 }
2684 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2685 if (!*(args[cur_arg+1])) {
2686 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2687 goto error;
2688 }
2689 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2690 if (strcmp(args[cur_arg], "body-lf") == 0)
2691 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2692 cur_arg++;
2693 body = args[cur_arg];
2694 }
2695 else if (strcmp(args[cur_arg], "comment") == 0) {
2696 if (!*(args[cur_arg+1])) {
2697 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2698 goto error;
2699 }
2700 cur_arg++;
2701 free(comment);
2702 comment = strdup(args[cur_arg]);
2703 if (!comment) {
2704 memprintf(errmsg, "out of memory");
2705 goto error;
2706 }
2707 }
2708 else {
2709 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2710 " but got '%s' as argument.", args[cur_arg]);
2711 goto error;
2712 }
2713 cur_arg++;
2714 }
2715
2716 hdrs[i].n = hdrs[i].v = IST_NULL;
2717
2718 chk = calloc(1, sizeof(*chk));
2719 if (!chk) {
2720 memprintf(errmsg, "out of memory");
2721 goto error;
2722 }
2723 chk->action = TCPCHK_ACT_SEND;
2724 chk->comment = comment; comment = NULL;
2725 chk->send.type = TCPCHK_SEND_HTTP;
2726 chk->send.http.flags = flags;
2727 LIST_INIT(&chk->send.http.hdrs);
2728
2729 if (meth) {
2730 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2731 chk->send.http.meth.str.area = strdup(meth);
2732 chk->send.http.meth.str.data = strlen(meth);
2733 if (!chk->send.http.meth.str.area) {
2734 memprintf(errmsg, "out of memory");
2735 goto error;
2736 }
2737 }
2738 if (uri) {
2739 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2740 LIST_INIT(&chk->send.http.uri_fmt);
2741 px->conf.args.ctx = ARGC_SRV;
2742 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2743 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2744 goto error;
2745 }
2746 }
2747 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002748 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002749 if (!isttest(chk->send.http.uri)) {
2750 memprintf(errmsg, "out of memory");
2751 goto error;
2752 }
2753 }
2754 }
2755 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002756 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002757 if (!isttest(chk->send.http.vsn)) {
2758 memprintf(errmsg, "out of memory");
2759 goto error;
2760 }
2761 }
2762 for (i = 0; istlen(hdrs[i].n); i++) {
2763 hdr = calloc(1, sizeof(*hdr));
2764 if (!hdr) {
2765 memprintf(errmsg, "out of memory");
2766 goto error;
2767 }
2768 LIST_INIT(&hdr->value);
2769 hdr->name = istdup(hdrs[i].n);
2770 if (!isttest(hdr->name)) {
2771 memprintf(errmsg, "out of memory");
2772 goto error;
2773 }
2774
2775 ist0(hdrs[i].v);
2776 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2777 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002778 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002779 hdr = NULL;
2780 }
2781
2782 if (body) {
2783 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2784 LIST_INIT(&chk->send.http.body_fmt);
2785 px->conf.args.ctx = ARGC_SRV;
2786 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2787 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2788 goto error;
2789 }
2790 }
2791 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002792 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002793 if (!isttest(chk->send.http.body)) {
2794 memprintf(errmsg, "out of memory");
2795 goto error;
2796 }
2797 }
2798 }
2799
2800 return chk;
2801
2802 error:
2803 free_tcpcheck_http_hdr(hdr);
2804 free_tcpcheck(chk, 0);
2805 free(comment);
2806 return NULL;
2807}
2808
2809/* Parses and creates a http-check comment rule. NULL is returned on error */
2810struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2811 const char *file, int line, char **errmsg)
2812{
2813 struct tcpcheck_rule *chk = NULL;
2814 char *comment = NULL;
2815
2816 if (!*(args[cur_arg+1])) {
2817 memprintf(errmsg, "expects a string as argument");
2818 goto error;
2819 }
2820 cur_arg++;
2821 comment = strdup(args[cur_arg]);
2822 if (!comment) {
2823 memprintf(errmsg, "out of memory");
2824 goto error;
2825 }
2826
2827 chk = calloc(1, sizeof(*chk));
2828 if (!chk) {
2829 memprintf(errmsg, "out of memory");
2830 goto error;
2831 }
2832 chk->action = TCPCHK_ACT_COMMENT;
2833 chk->comment = comment;
2834 return chk;
2835
2836 error:
2837 free(comment);
2838 return NULL;
2839}
2840
2841/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2842 * on error. <proto> is set to the right protocol flags (covered by the
2843 * TCPCHK_RULES_PROTO_CHK mask).
2844 */
2845struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2846 struct list *rules, unsigned int proto,
2847 const char *file, int line, char **errmsg)
2848{
2849 struct tcpcheck_rule *prev_check, *chk = NULL;
2850 struct sample_expr *status_expr = NULL;
2851 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2852 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2853 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2854 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2855 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2856 unsigned int flags = 0;
2857 long min_recv = -1;
2858 int inverse = 0;
2859
2860 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2861 if (!*(args[cur_arg+1])) {
2862 memprintf(errmsg, "expects at least a matching pattern as arguments");
2863 goto error;
2864 }
2865
2866 cur_arg++;
2867 while (*(args[cur_arg])) {
2868 int in_pattern = 0;
2869
2870 rescan:
2871 if (strcmp(args[cur_arg], "min-recv") == 0) {
2872 if (in_pattern) {
2873 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2874 goto error;
2875 }
2876 if (!*(args[cur_arg+1])) {
2877 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2878 goto error;
2879 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002880 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002881 cur_arg++;
2882 min_recv = atol(args[cur_arg]);
2883 if (min_recv < -1 || min_recv > INT_MAX) {
2884 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2885 goto error;
2886 }
2887 }
2888 else if (*(args[cur_arg]) == '!') {
2889 in_pattern = 1;
2890 while (*(args[cur_arg]) == '!') {
2891 inverse = !inverse;
2892 args[cur_arg]++;
2893 }
2894 if (!*(args[cur_arg]))
2895 cur_arg++;
2896 goto rescan;
2897 }
2898 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2899 if (type != TCPCHK_EXPECT_UNDEF) {
2900 memprintf(errmsg, "only on pattern expected");
2901 goto error;
2902 }
2903 if (proto != TCPCHK_RULES_HTTP_CHK)
2904 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2905 else
2906 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2907
2908 if (!*(args[cur_arg+1])) {
2909 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2910 goto error;
2911 }
2912 cur_arg++;
2913 pattern = args[cur_arg];
2914 }
2915 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2916 if (proto == TCPCHK_RULES_HTTP_CHK)
2917 goto bad_http_kw;
2918 if (type != TCPCHK_EXPECT_UNDEF) {
2919 memprintf(errmsg, "only on pattern expected");
2920 goto error;
2921 }
2922 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2923
2924 if (!*(args[cur_arg+1])) {
2925 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2926 goto error;
2927 }
2928 cur_arg++;
2929 pattern = args[cur_arg];
2930 }
2931 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2932 if (type != TCPCHK_EXPECT_UNDEF) {
2933 memprintf(errmsg, "only on pattern expected");
2934 goto error;
2935 }
2936 if (proto != TCPCHK_RULES_HTTP_CHK)
2937 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2938 else {
2939 if (*(args[cur_arg]) != 's')
2940 goto bad_http_kw;
2941 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2942 }
2943
2944 if (!*(args[cur_arg+1])) {
2945 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2946 goto error;
2947 }
2948 cur_arg++;
2949 pattern = args[cur_arg];
2950 }
2951 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
2952 if (proto != TCPCHK_RULES_HTTP_CHK)
2953 goto bad_tcp_kw;
2954 if (type != TCPCHK_EXPECT_UNDEF) {
2955 memprintf(errmsg, "only on pattern expected");
2956 goto error;
2957 }
2958 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
2959
2960 if (!*(args[cur_arg+1])) {
2961 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2962 goto error;
2963 }
2964 cur_arg++;
2965 pattern = args[cur_arg];
2966 }
2967 else if (strcmp(args[cur_arg], "custom") == 0) {
2968 if (in_pattern) {
2969 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2970 goto error;
2971 }
2972 if (type != TCPCHK_EXPECT_UNDEF) {
2973 memprintf(errmsg, "only on pattern expected");
2974 goto error;
2975 }
2976 type = TCPCHK_EXPECT_CUSTOM;
2977 }
2978 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
2979 int orig_arg = cur_arg;
2980
2981 if (proto != TCPCHK_RULES_HTTP_CHK)
2982 goto bad_tcp_kw;
2983 if (type != TCPCHK_EXPECT_UNDEF) {
2984 memprintf(errmsg, "only on pattern expected");
2985 goto error;
2986 }
2987 type = TCPCHK_EXPECT_HTTP_HEADER;
2988
2989 if (strcmp(args[cur_arg], "fhdr") == 0)
2990 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
2991
2992 /* Parse the name pattern, mandatory */
2993 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
2994 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
2995 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
2996 args[orig_arg]);
2997 goto error;
2998 }
2999
3000 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3001 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3002
3003 cur_arg += 2;
3004 if (strcmp(args[cur_arg], "-m") == 0) {
3005 if (!*(args[cur_arg+1])) {
3006 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3007 args[orig_arg], args[cur_arg]);
3008 goto error;
3009 }
3010 if (strcmp(args[cur_arg+1], "str") == 0)
3011 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3012 else if (strcmp(args[cur_arg+1], "beg") == 0)
3013 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3014 else if (strcmp(args[cur_arg+1], "end") == 0)
3015 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3016 else if (strcmp(args[cur_arg+1], "sub") == 0)
3017 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3018 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3019 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3020 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3021 args[orig_arg]);
3022 goto error;
3023 }
3024 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3025 }
3026 else {
3027 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3028 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3029 goto error;
3030 }
3031 cur_arg += 2;
3032 }
3033 else
3034 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3035 npat = args[cur_arg];
3036
3037 if (!*(args[cur_arg+1]) ||
3038 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3039 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3040 goto next;
3041 }
3042 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3043 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3044
3045 /* Parse the value pattern, optional */
3046 if (strcmp(args[cur_arg+2], "-m") == 0) {
3047 cur_arg += 2;
3048 if (!*(args[cur_arg+1])) {
3049 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3050 args[orig_arg], args[cur_arg]);
3051 goto error;
3052 }
3053 if (strcmp(args[cur_arg+1], "str") == 0)
3054 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3055 else if (strcmp(args[cur_arg+1], "beg") == 0)
3056 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3057 else if (strcmp(args[cur_arg+1], "end") == 0)
3058 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3059 else if (strcmp(args[cur_arg+1], "sub") == 0)
3060 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3061 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3062 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3063 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3064 args[orig_arg]);
3065 goto error;
3066 }
3067 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3068 }
3069 else {
3070 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3071 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3072 goto error;
3073 }
3074 }
3075 else
3076 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3077
3078 if (!*(args[cur_arg+2])) {
3079 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3080 goto error;
3081 }
3082 vpat = args[cur_arg+2];
3083 cur_arg += 2;
3084 }
3085 else if (strcmp(args[cur_arg], "comment") == 0) {
3086 if (in_pattern) {
3087 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3088 goto error;
3089 }
3090 if (!*(args[cur_arg+1])) {
3091 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3092 goto error;
3093 }
3094 cur_arg++;
3095 free(comment);
3096 comment = strdup(args[cur_arg]);
3097 if (!comment) {
3098 memprintf(errmsg, "out of memory");
3099 goto error;
3100 }
3101 }
3102 else if (strcmp(args[cur_arg], "on-success") == 0) {
3103 if (in_pattern) {
3104 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3105 goto error;
3106 }
3107 if (!*(args[cur_arg+1])) {
3108 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3109 goto error;
3110 }
3111 cur_arg++;
3112 on_success_msg = args[cur_arg];
3113 }
3114 else if (strcmp(args[cur_arg], "on-error") == 0) {
3115 if (in_pattern) {
3116 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3117 goto error;
3118 }
3119 if (!*(args[cur_arg+1])) {
3120 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3121 goto error;
3122 }
3123 cur_arg++;
3124 on_error_msg = args[cur_arg];
3125 }
3126 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3127 if (in_pattern) {
3128 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3129 goto error;
3130 }
3131 if (!*(args[cur_arg+1])) {
3132 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3133 goto error;
3134 }
3135 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3136 ok_st = HCHK_STATUS_L7OKD;
3137 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3138 ok_st = HCHK_STATUS_L7OKCD;
3139 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3140 ok_st = HCHK_STATUS_L6OK;
3141 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3142 ok_st = HCHK_STATUS_L4OK;
3143 else {
3144 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3145 args[cur_arg], args[cur_arg+1]);
3146 goto error;
3147 }
3148 cur_arg++;
3149 }
3150 else if (strcmp(args[cur_arg], "error-status") == 0) {
3151 if (in_pattern) {
3152 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3153 goto error;
3154 }
3155 if (!*(args[cur_arg+1])) {
3156 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3157 goto error;
3158 }
3159 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3160 err_st = HCHK_STATUS_L7RSP;
3161 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3162 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003163 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3164 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003165 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3166 err_st = HCHK_STATUS_L6RSP;
3167 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3168 err_st = HCHK_STATUS_L4CON;
3169 else {
3170 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3171 args[cur_arg], args[cur_arg+1]);
3172 goto error;
3173 }
3174 cur_arg++;
3175 }
3176 else if (strcmp(args[cur_arg], "status-code") == 0) {
3177 int idx = 0;
3178
3179 if (in_pattern) {
3180 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3181 goto error;
3182 }
3183 if (!*(args[cur_arg+1])) {
3184 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3185 goto error;
3186 }
3187
3188 cur_arg++;
3189 release_sample_expr(status_expr);
3190 px->conf.args.ctx = ARGC_SRV;
3191 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3192 file, line, errmsg, &px->conf.args, NULL);
3193 if (!status_expr) {
3194 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3195 goto error;
3196 }
3197 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3198 memprintf(errmsg, "error detected while parsing status-code expression : "
3199 " fetch method '%s' extracts information from '%s', "
3200 "none of which is available here.\n",
3201 args[cur_arg], sample_src_names(status_expr->fetch->use));
3202 goto error;
3203 }
3204 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3205 }
3206 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3207 if (in_pattern) {
3208 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3209 goto error;
3210 }
3211 if (!*(args[cur_arg+1])) {
3212 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3213 goto error;
3214 }
3215 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3216 tout_st = HCHK_STATUS_L7TOUT;
3217 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3218 tout_st = HCHK_STATUS_L6TOUT;
3219 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3220 tout_st = HCHK_STATUS_L4TOUT;
3221 else {
3222 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3223 args[cur_arg], args[cur_arg+1]);
3224 goto error;
3225 }
3226 cur_arg++;
3227 }
3228 else {
3229 if (proto == TCPCHK_RULES_HTTP_CHK) {
3230 bad_http_kw:
3231 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3232 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3233 }
3234 else {
3235 bad_tcp_kw:
3236 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3237 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3238 }
3239 goto error;
3240 }
3241 next:
3242 cur_arg++;
3243 }
3244
3245 chk = calloc(1, sizeof(*chk));
3246 if (!chk) {
3247 memprintf(errmsg, "out of memory");
3248 goto error;
3249 }
3250 chk->action = TCPCHK_ACT_EXPECT;
3251 LIST_INIT(&chk->expect.onerror_fmt);
3252 LIST_INIT(&chk->expect.onsuccess_fmt);
3253 chk->comment = comment; comment = NULL;
3254 chk->expect.type = type;
3255 chk->expect.min_recv = min_recv;
3256 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3257 chk->expect.ok_status = ok_st;
3258 chk->expect.err_status = err_st;
3259 chk->expect.tout_status = tout_st;
3260 chk->expect.status_expr = status_expr; status_expr = NULL;
3261
3262 if (on_success_msg) {
3263 px->conf.args.ctx = ARGC_SRV;
3264 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3265 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3266 goto error;
3267 }
3268 }
3269 if (on_error_msg) {
3270 px->conf.args.ctx = ARGC_SRV;
3271 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3272 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3273 goto error;
3274 }
3275 }
3276
3277 switch (chk->expect.type) {
3278 case TCPCHK_EXPECT_HTTP_STATUS: {
3279 const char *p = pattern;
3280 unsigned int c1,c2;
3281
3282 chk->expect.codes.codes = NULL;
3283 chk->expect.codes.num = 0;
3284 while (1) {
3285 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3286 if (*p == '-') {
3287 p++;
3288 c2 = read_uint(&p, pattern + strlen(pattern));
3289 }
3290 if (c1 > c2) {
3291 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3292 goto error;
3293 }
3294
3295 chk->expect.codes.num++;
3296 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3297 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3298 if (!chk->expect.codes.codes) {
3299 memprintf(errmsg, "out of memory");
3300 goto error;
3301 }
3302 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3303 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3304
3305 if (*p == '\0')
3306 break;
3307 if (*p != ',') {
3308 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3309 goto error;
3310 }
3311 p++;
3312 }
3313 break;
3314 }
3315 case TCPCHK_EXPECT_STRING:
3316 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003317 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003318 if (!isttest(chk->expect.data)) {
3319 memprintf(errmsg, "out of memory");
3320 goto error;
3321 }
3322 break;
3323 case TCPCHK_EXPECT_BINARY: {
3324 int len = chk->expect.data.len;
3325
3326 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3327 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3328 goto error;
3329 }
3330 chk->expect.data.len = len;
3331 break;
3332 }
3333 case TCPCHK_EXPECT_STRING_REGEX:
3334 case TCPCHK_EXPECT_BINARY_REGEX:
3335 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3336 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3337 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3338 if (!chk->expect.regex)
3339 goto error;
3340 break;
3341
3342 case TCPCHK_EXPECT_STRING_LF:
3343 case TCPCHK_EXPECT_BINARY_LF:
3344 case TCPCHK_EXPECT_HTTP_BODY_LF:
3345 LIST_INIT(&chk->expect.fmt);
3346 px->conf.args.ctx = ARGC_SRV;
3347 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3348 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3349 goto error;
3350 }
3351 break;
3352
3353 case TCPCHK_EXPECT_HTTP_HEADER:
3354 if (!npat) {
3355 memprintf(errmsg, "unexpected error, undefined header name pattern");
3356 goto error;
3357 }
3358 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3359 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3360 if (!chk->expect.hdr.name_re)
3361 goto error;
3362 }
3363 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3364 px->conf.args.ctx = ARGC_SRV;
3365 LIST_INIT(&chk->expect.hdr.name_fmt);
3366 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3367 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3368 goto error;
3369 }
3370 }
3371 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003372 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003373 if (!isttest(chk->expect.hdr.name)) {
3374 memprintf(errmsg, "out of memory");
3375 goto error;
3376 }
3377 }
3378
3379 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3380 chk->expect.hdr.value = IST_NULL;
3381 break;
3382 }
3383
3384 if (!vpat) {
3385 memprintf(errmsg, "unexpected error, undefined header value pattern");
3386 goto error;
3387 }
3388 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3389 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3390 if (!chk->expect.hdr.value_re)
3391 goto error;
3392 }
3393 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3394 px->conf.args.ctx = ARGC_SRV;
3395 LIST_INIT(&chk->expect.hdr.value_fmt);
3396 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3397 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3398 goto error;
3399 }
3400 }
3401 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003402 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003403 if (!isttest(chk->expect.hdr.value)) {
3404 memprintf(errmsg, "out of memory");
3405 goto error;
3406 }
3407 }
3408
3409 break;
3410 case TCPCHK_EXPECT_CUSTOM:
3411 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3412 break;
3413 case TCPCHK_EXPECT_UNDEF:
3414 memprintf(errmsg, "pattern not found");
3415 goto error;
3416 }
3417
3418 /* All tcp-check expect points back to the first inverse expect rule in
3419 * a chain of one or more expect rule, potentially itself.
3420 */
3421 chk->expect.head = chk;
3422 list_for_each_entry_rev(prev_check, rules, list) {
3423 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3424 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3425 chk->expect.head = prev_check;
3426 continue;
3427 }
3428 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3429 break;
3430 }
3431 return chk;
3432
3433 error:
3434 free_tcpcheck(chk, 0);
3435 free(comment);
3436 release_sample_expr(status_expr);
3437 return NULL;
3438}
3439
3440/* Overwrites fields of the old http send rule with those of the new one. When
3441 * replaced, old values are freed and replaced by the new ones. New values are
3442 * not copied but transferred. At the end <new> should be empty and can be
3443 * safely released. This function never fails.
3444 */
3445void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3446{
3447 struct logformat_node *lf, *lfb;
3448 struct tcpcheck_http_hdr *hdr, *bhdr;
3449
3450
3451 if (new->send.http.meth.str.area) {
3452 free(old->send.http.meth.str.area);
3453 old->send.http.meth.meth = new->send.http.meth.meth;
3454 old->send.http.meth.str.area = new->send.http.meth.str.area;
3455 old->send.http.meth.str.data = new->send.http.meth.str.data;
3456 new->send.http.meth.str = BUF_NULL;
3457 }
3458
3459 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3460 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3461 istfree(&old->send.http.uri);
3462 else
3463 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3464 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3465 old->send.http.uri = new->send.http.uri;
3466 new->send.http.uri = IST_NULL;
3467 }
3468 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3469 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3470 istfree(&old->send.http.uri);
3471 else
3472 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3473 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3474 LIST_INIT(&old->send.http.uri_fmt);
3475 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003476 LIST_DELETE(&lf->list);
3477 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003478 }
3479 }
3480
3481 if (isttest(new->send.http.vsn)) {
3482 istfree(&old->send.http.vsn);
3483 old->send.http.vsn = new->send.http.vsn;
3484 new->send.http.vsn = IST_NULL;
3485 }
3486
3487 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3488 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003489 LIST_DELETE(&hdr->list);
3490 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003491 }
3492
3493 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3494 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3495 istfree(&old->send.http.body);
3496 else
3497 free_tcpcheck_fmt(&old->send.http.body_fmt);
3498 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3499 old->send.http.body = new->send.http.body;
3500 new->send.http.body = IST_NULL;
3501 }
3502 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3503 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3504 istfree(&old->send.http.body);
3505 else
3506 free_tcpcheck_fmt(&old->send.http.body_fmt);
3507 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3508 LIST_INIT(&old->send.http.body_fmt);
3509 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003510 LIST_DELETE(&lf->list);
3511 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003512 }
3513 }
3514}
3515
3516/* Internal function used to add an http-check rule in a list during the config
3517 * parsing step. Depending on its type, and the previously inserted rules, a
3518 * specific action may be performed or an error may be reported. This functions
3519 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3520 * message.
3521 */
3522int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3523{
3524 struct tcpcheck_rule *r;
3525
3526 /* the implicit send rule coming from an "option httpchk" line must be
3527 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003528 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003529 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003530 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003531 * sure the ruleset remains valid.
3532 */
3533
3534 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3535 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3536 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3537 * following tests are performed :
3538 *
3539 * 1- If there is no such rule or if it is not a send rule, the implicit send
3540 * rule is pushed in front of the ruleset
3541 *
3542 * 2- If it is another implicit send rule, it is replaced with the new one.
3543 *
3544 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3545 * both, overwriting the old send rule (the explicit one) with info of the
3546 * new send rule (the implicit one).
3547 */
3548 r = get_first_tcpcheck_rule(rules);
3549 if (r && r->action == TCPCHK_ACT_CONNECT)
3550 r = get_next_tcpcheck_rule(rules, r);
3551 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003552 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003553 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003554 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003555 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003556 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003557 }
3558 else {
3559 tcpcheck_overwrite_send_http_rule(r, chk);
3560 free_tcpcheck(chk, 0);
3561 }
3562 }
3563 else {
3564 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3565 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3566 * with an existing implicit send rule, if any. At the end, if there is no error,
3567 * the rule is appended to the list.
3568 */
3569
3570 r = get_last_tcpcheck_rule(rules);
3571 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3572 /* no error */;
3573 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3574 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3575 chk->index+1);
3576 return 0;
3577 }
3578 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3579 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3580 chk->index+1);
3581 return 0;
3582 }
3583 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3584 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3585 chk->index+1);
3586 return 0;
3587 }
3588
3589 if (chk->action == TCPCHK_ACT_SEND) {
3590 r = get_first_tcpcheck_rule(rules);
3591 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3592 tcpcheck_overwrite_send_http_rule(r, chk);
3593 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003594 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003595 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3596 chk = r;
3597 }
3598 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003599 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003600 }
3601 return 1;
3602}
3603
3604/* Check tcp-check health-check configuration for the proxy <px>. */
3605static int check_proxy_tcpcheck(struct proxy *px)
3606{
3607 struct tcpcheck_rule *chk, *back;
3608 char *comment = NULL, *errmsg = NULL;
3609 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003610 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003611
3612 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3613 deinit_proxy_tcpcheck(px);
3614 goto out;
3615 }
3616
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003617 ha_free(&px->check_command);
3618 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003619
3620 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003621 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003622 ret |= ERR_ALERT | ERR_FATAL;
3623 goto out;
3624 }
3625
3626 /* HTTP ruleset only : */
3627 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3628 struct tcpcheck_rule *next;
3629
3630 /* move remaining implicit send rule from "option httpchk" line to the right place.
3631 * If such rule exists, it must be the first one. In this case, the rule is moved
3632 * after the first connect rule, if any. Otherwise, nothing is done.
3633 */
3634 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3635 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3636 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3637 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003638 LIST_DELETE(&chk->list);
3639 LIST_INSERT(&next->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003640 chk->index = next->index;
3641 }
3642 }
3643
3644 /* add implicit expect rule if the last one is a send. It is inherited from previous
3645 * versions where the http expect rule was optional. Now it is possible to chained
3646 * send/expect rules but the last expect may still be implicit.
3647 */
3648 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3649 if (chk && chk->action == TCPCHK_ACT_SEND) {
3650 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3651 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3652 px->conf.file, px->conf.line, &errmsg);
3653 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003654 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003655 "(%s).\n", px->id, errmsg);
3656 free(errmsg);
3657 ret |= ERR_ALERT | ERR_FATAL;
3658 goto out;
3659 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003660 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003661 next->index = chk->index;
3662 }
3663 }
3664
3665 /* For all ruleset: */
3666
3667 /* If there is no connect rule preceding all send / expect rules, an
3668 * implicit one is inserted before all others.
3669 */
3670 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3671 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3672 chk = calloc(1, sizeof(*chk));
3673 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003674 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003675 "(out of memory).\n", px->id);
3676 ret |= ERR_ALERT | ERR_FATAL;
3677 goto out;
3678 }
3679 chk->action = TCPCHK_ACT_CONNECT;
3680 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003681 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003682 }
3683
3684 /* Remove all comment rules. To do so, when a such rule is found, the
3685 * comment is assigned to the following rule(s).
3686 */
3687 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003688 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3689 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003690
3691 prev_action = chk->action;
3692 switch (chk->action) {
3693 case TCPCHK_ACT_COMMENT:
3694 free(comment);
3695 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003696 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003697 free(chk);
3698 break;
3699 case TCPCHK_ACT_CONNECT:
3700 if (!chk->comment && comment)
3701 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003702 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003703 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003704 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003705 break;
3706 case TCPCHK_ACT_SEND:
3707 case TCPCHK_ACT_EXPECT:
3708 if (!chk->comment && comment)
3709 chk->comment = strdup(comment);
3710 break;
3711 }
3712 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003713 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003714
3715 out:
3716 return ret;
3717}
3718
3719void deinit_proxy_tcpcheck(struct proxy *px)
3720{
3721 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3722 px->tcpcheck_rules.flags = 0;
3723 px->tcpcheck_rules.list = NULL;
3724}
3725
3726static void deinit_tcpchecks()
3727{
3728 struct tcpcheck_ruleset *rs;
3729 struct tcpcheck_rule *r, *rb;
3730 struct ebpt_node *node, *next;
3731
3732 node = ebpt_first(&shared_tcpchecks);
3733 while (node) {
3734 next = ebpt_next(node);
3735 ebpt_delete(node);
3736 free(node->key);
3737 rs = container_of(node, typeof(*rs), node);
3738 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003739 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003740 free_tcpcheck(r, 0);
3741 }
3742 free(rs);
3743 node = next;
3744 }
3745}
3746
3747int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3748{
3749 struct tcpcheck_rule *tcpcheck, *prev_check;
3750 struct tcpcheck_expect *expect;
3751
Willy Tarreau6922e552021-03-22 21:11:45 +01003752 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003753 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003754 tcpcheck->action = TCPCHK_ACT_EXPECT;
3755
3756 expect = &tcpcheck->expect;
3757 expect->type = TCPCHK_EXPECT_STRING;
3758 LIST_INIT(&expect->onerror_fmt);
3759 LIST_INIT(&expect->onsuccess_fmt);
3760 expect->ok_status = HCHK_STATUS_L7OKD;
3761 expect->err_status = HCHK_STATUS_L7RSP;
3762 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003763 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003764 if (!isttest(expect->data)) {
3765 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3766 return 0;
3767 }
3768
3769 /* All tcp-check expect points back to the first inverse expect rule
3770 * in a chain of one or more expect rule, potentially itself.
3771 */
3772 tcpcheck->expect.head = tcpcheck;
3773 list_for_each_entry_rev(prev_check, rules->list, list) {
3774 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3775 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3776 tcpcheck->expect.head = prev_check;
3777 continue;
3778 }
3779 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3780 break;
3781 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003782 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003783 return 1;
3784}
3785
3786int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3787{
3788 struct tcpcheck_rule *tcpcheck;
3789 struct tcpcheck_send *send;
3790 const char *in;
3791 char *dst;
3792 int i;
3793
Willy Tarreau6922e552021-03-22 21:11:45 +01003794 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003795 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003796 tcpcheck->action = TCPCHK_ACT_SEND;
3797
3798 send = &tcpcheck->send;
3799 send->type = TCPCHK_SEND_STRING;
3800
3801 for (i = 0; strs[i]; i++)
3802 send->data.len += strlen(strs[i]);
3803
3804 send->data.ptr = malloc(istlen(send->data) + 1);
3805 if (!isttest(send->data)) {
3806 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3807 return 0;
3808 }
3809
3810 dst = istptr(send->data);
3811 for (i = 0; strs[i]; i++)
3812 for (in = strs[i]; (*dst = *in++); dst++);
3813 *dst = 0;
3814
Willy Tarreau2b718102021-04-21 07:32:39 +02003815 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003816 return 1;
3817}
3818
3819/* Parses the "tcp-check" proxy keyword */
3820static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003821 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003822 char **errmsg)
3823{
3824 struct tcpcheck_ruleset *rs = NULL;
3825 struct tcpcheck_rule *chk = NULL;
3826 int index, cur_arg, ret = 0;
3827
3828 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3829 ret = 1;
3830
3831 /* Deduce the ruleset name from the proxy info */
3832 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3833 ((curpx == defpx) ? "defaults" : curpx->id),
3834 curpx->conf.file, curpx->conf.line);
3835
3836 rs = find_tcpcheck_ruleset(b_orig(&trash));
3837 if (rs == NULL) {
3838 rs = create_tcpcheck_ruleset(b_orig(&trash));
3839 if (rs == NULL) {
3840 memprintf(errmsg, "out of memory.\n");
3841 goto error;
3842 }
3843 }
3844
3845 index = 0;
3846 if (!LIST_ISEMPTY(&rs->rules)) {
3847 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3848 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003849 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003850 }
3851
3852 cur_arg = 1;
3853 if (strcmp(args[cur_arg], "connect") == 0)
3854 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3855 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3856 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3857 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3858 else if (strcmp(args[cur_arg], "expect") == 0)
3859 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3860 else if (strcmp(args[cur_arg], "comment") == 0)
3861 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3862 else {
3863 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3864
3865 if (!kw) {
3866 action_kw_tcp_check_build_list(&trash);
3867 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3868 "%s%s. but got '%s'",
3869 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3870 goto error;
3871 }
3872 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3873 }
3874
3875 if (!chk) {
3876 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3877 goto error;
3878 }
3879 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3880
3881 /* No error: add the tcp-check rule in the list */
3882 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003883 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003884
3885 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3886 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3887 /* Use this ruleset if the proxy already has tcp-check enabled */
3888 curpx->tcpcheck_rules.list = &rs->rules;
3889 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3890 }
3891 else {
3892 /* mark this ruleset as unused for now */
3893 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3894 }
3895
3896 return ret;
3897
3898 error:
3899 free_tcpcheck(chk, 0);
3900 free_tcpcheck_ruleset(rs);
3901 return -1;
3902}
3903
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003904/* Parses the "http-check" proxy keyword */
3905static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003906 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003907 char **errmsg)
3908{
3909 struct tcpcheck_ruleset *rs = NULL;
3910 struct tcpcheck_rule *chk = NULL;
3911 int index, cur_arg, ret = 0;
3912
3913 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3914 ret = 1;
3915
3916 cur_arg = 1;
3917 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3918 /* enable a graceful server shutdown on an HTTP 404 response */
3919 curpx->options |= PR_O_DISABLE404;
3920 if (too_many_args(1, args, errmsg, NULL))
3921 goto error;
3922 goto out;
3923 }
3924 else if (strcmp(args[cur_arg], "send-state") == 0) {
3925 /* enable emission of the apparent state of a server in HTTP checks */
3926 curpx->options2 |= PR_O2_CHK_SNDST;
3927 if (too_many_args(1, args, errmsg, NULL))
3928 goto error;
3929 goto out;
3930 }
3931
3932 /* Deduce the ruleset name from the proxy info */
3933 chunk_printf(&trash, "*http-check-%s_%s-%d",
3934 ((curpx == defpx) ? "defaults" : curpx->id),
3935 curpx->conf.file, curpx->conf.line);
3936
3937 rs = find_tcpcheck_ruleset(b_orig(&trash));
3938 if (rs == NULL) {
3939 rs = create_tcpcheck_ruleset(b_orig(&trash));
3940 if (rs == NULL) {
3941 memprintf(errmsg, "out of memory.\n");
3942 goto error;
3943 }
3944 }
3945
3946 index = 0;
3947 if (!LIST_ISEMPTY(&rs->rules)) {
3948 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3949 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
3950 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003951 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003952 }
3953
3954 if (strcmp(args[cur_arg], "connect") == 0)
3955 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3956 else if (strcmp(args[cur_arg], "send") == 0)
3957 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3958 else if (strcmp(args[cur_arg], "expect") == 0)
3959 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
3960 file, line, errmsg);
3961 else if (strcmp(args[cur_arg], "comment") == 0)
3962 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3963 else {
3964 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3965
3966 if (!kw) {
3967 action_kw_tcp_check_build_list(&trash);
3968 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
3969 " 'send', 'expect'%s%s. but got '%s'",
3970 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3971 goto error;
3972 }
3973 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3974 }
3975
3976 if (!chk) {
3977 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3978 goto error;
3979 }
3980 ret = (*errmsg != NULL); /* Handle warning */
3981
3982 chk->index = index;
3983 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3984 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3985 /* Use this ruleset if the proxy already has http-check enabled */
3986 curpx->tcpcheck_rules.list = &rs->rules;
3987 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
3988 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
3989 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3990 curpx->tcpcheck_rules.list = NULL;
3991 goto error;
3992 }
3993 }
3994 else {
3995 /* mark this ruleset as unused for now */
3996 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02003997 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003998 }
3999
4000 out:
4001 return ret;
4002
4003 error:
4004 free_tcpcheck(chk, 0);
4005 free_tcpcheck_ruleset(rs);
4006 return -1;
4007}
4008
4009/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004010int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004011 const char *file, int line)
4012{
4013 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4014 static char *redis_res = "+PONG\r\n";
4015
4016 struct tcpcheck_ruleset *rs = NULL;
4017 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4018 struct tcpcheck_rule *chk;
4019 char *errmsg = NULL;
4020 int err_code = 0;
4021
4022 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4023 err_code |= ERR_WARN;
4024
4025 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4026 goto out;
4027
4028 curpx->options2 &= ~PR_O2_CHK_ANY;
4029 curpx->options2 |= PR_O2_TCPCHK_CHK;
4030
4031 free_tcpcheck_vars(&rules->preset_vars);
4032 rules->list = NULL;
4033 rules->flags = 0;
4034
4035 rs = find_tcpcheck_ruleset("*redis-check");
4036 if (rs)
4037 goto ruleset_found;
4038
4039 rs = create_tcpcheck_ruleset("*redis-check");
4040 if (rs == NULL) {
4041 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4042 goto error;
4043 }
4044
4045 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4046 1, curpx, &rs->rules, file, line, &errmsg);
4047 if (!chk) {
4048 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4049 goto error;
4050 }
4051 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004052 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004053
4054 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4055 "error-status", "L7STS",
4056 "on-error", "%[res.payload(0,0),cut_crlf]",
4057 "on-success", "Redis server is ok",
4058 ""},
4059 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4060 if (!chk) {
4061 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4062 goto error;
4063 }
4064 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004065 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004066
4067 ruleset_found:
4068 rules->list = &rs->rules;
4069 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4070 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4071
4072 out:
4073 free(errmsg);
4074 return err_code;
4075
4076 error:
4077 free_tcpcheck_ruleset(rs);
4078 err_code |= ERR_ALERT | ERR_FATAL;
4079 goto out;
4080}
4081
4082
4083/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004084int 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 +01004085 const char *file, int line)
4086{
4087 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4088 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4089 *
4090 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4091 */
4092 static char sslv3_client_hello[] = {
4093 "16" /* ContentType : 0x16 = Handshake */
4094 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4095 "0079" /* ContentLength : 0x79 bytes after this one */
4096 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4097 "000075" /* HandshakeLength : 0x75 bytes after this one */
4098 "0300" /* Hello Version : 0x0300 = v3 */
4099 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4100 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4101 "00" /* Session ID length : empty (no session ID) */
4102 "004E" /* Cipher Suite Length : 78 bytes after this one */
4103 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4104 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4105 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4106 "000D" "000E" "000F" "0010" /* various bit lengths, */
4107 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4108 "0015" "0016" "0017" "0018"
4109 "0019" "001A" "001B" "002F"
4110 "0030" "0031" "0032" "0033"
4111 "0034" "0035" "0036" "0037"
4112 "0038" "0039" "003A"
4113 "01" /* Compression Length : 0x01 = 1 byte for types */
4114 "00" /* Compression Type : 0x00 = NULL compression */
4115 };
4116
4117 struct tcpcheck_ruleset *rs = NULL;
4118 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4119 struct tcpcheck_rule *chk;
4120 char *errmsg = NULL;
4121 int err_code = 0;
4122
4123 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4124 err_code |= ERR_WARN;
4125
4126 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4127 goto out;
4128
4129 curpx->options2 &= ~PR_O2_CHK_ANY;
4130 curpx->options2 |= PR_O2_TCPCHK_CHK;
4131
4132 free_tcpcheck_vars(&rules->preset_vars);
4133 rules->list = NULL;
4134 rules->flags = 0;
4135
4136 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4137 if (rs)
4138 goto ruleset_found;
4139
4140 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4141 if (rs == NULL) {
4142 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4143 goto error;
4144 }
4145
4146 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4147 1, curpx, &rs->rules, file, line, &errmsg);
4148 if (!chk) {
4149 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4150 goto error;
4151 }
4152 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004153 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004154
4155 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4156 "min-recv", "5", "ok-status", "L6OK",
4157 "error-status", "L6RSP", "tout-status", "L6TOUT",
4158 ""},
4159 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4160 if (!chk) {
4161 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4162 goto error;
4163 }
4164 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004165 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004166
4167 ruleset_found:
4168 rules->list = &rs->rules;
4169 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4170 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4171
4172 out:
4173 free(errmsg);
4174 return err_code;
4175
4176 error:
4177 free_tcpcheck_ruleset(rs);
4178 err_code |= ERR_ALERT | ERR_FATAL;
4179 goto out;
4180}
4181
4182/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004183int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004184 const char *file, int line)
4185{
4186 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4187
4188 struct tcpcheck_ruleset *rs = NULL;
4189 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4190 struct tcpcheck_rule *chk;
4191 struct tcpcheck_var *var = NULL;
4192 char *cmd = NULL, *errmsg = NULL;
4193 int err_code = 0;
4194
4195 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4196 err_code |= ERR_WARN;
4197
4198 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4199 goto out;
4200
4201 curpx->options2 &= ~PR_O2_CHK_ANY;
4202 curpx->options2 |= PR_O2_TCPCHK_CHK;
4203
4204 free_tcpcheck_vars(&rules->preset_vars);
4205 rules->list = NULL;
4206 rules->flags = 0;
4207
4208 cur_arg += 2;
4209 if (*args[cur_arg] && *args[cur_arg+1] &&
4210 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4211 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4212 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4213 if (cmd)
4214 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4215 }
4216 else {
4217 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4218 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4219 cmd = strdup("HELO localhost");
4220 }
4221
4222 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4223 if (cmd == NULL || var == NULL) {
4224 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4225 goto error;
4226 }
4227 var->data.type = SMP_T_STR;
4228 var->data.u.str.area = cmd;
4229 var->data.u.str.data = strlen(cmd);
4230 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004231 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004232 cmd = NULL;
4233 var = NULL;
4234
4235 rs = find_tcpcheck_ruleset("*smtp-check");
4236 if (rs)
4237 goto ruleset_found;
4238
4239 rs = create_tcpcheck_ruleset("*smtp-check");
4240 if (rs == NULL) {
4241 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4242 goto error;
4243 }
4244
4245 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4246 1, curpx, &rs->rules, file, line, &errmsg);
4247 if (!chk) {
4248 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4249 goto error;
4250 }
4251 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004252 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004253
4254 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4255 "min-recv", "4",
4256 "error-status", "L7RSP",
4257 "on-error", "%[res.payload(0,0),cut_crlf]",
4258 ""},
4259 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4260 if (!chk) {
4261 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4262 goto error;
4263 }
4264 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004265 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004266
4267 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4268 "min-recv", "4",
4269 "error-status", "L7STS",
4270 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4271 "status-code", "res.payload(0,3)",
4272 ""},
4273 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4274 if (!chk) {
4275 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4276 goto error;
4277 }
4278 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004279 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004280
4281 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4282 1, curpx, &rs->rules, file, line, &errmsg);
4283 if (!chk) {
4284 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4285 goto error;
4286 }
4287 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004288 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004289
4290 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4291 "min-recv", "4",
4292 "error-status", "L7STS",
4293 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4294 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4295 "status-code", "res.payload(0,3)",
4296 ""},
4297 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4298 if (!chk) {
4299 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4300 goto error;
4301 }
4302 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004303 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004304
4305 ruleset_found:
4306 rules->list = &rs->rules;
4307 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4308 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4309
4310 out:
4311 free(errmsg);
4312 return err_code;
4313
4314 error:
4315 free(cmd);
4316 free(var);
4317 free_tcpcheck_vars(&rules->preset_vars);
4318 free_tcpcheck_ruleset(rs);
4319 err_code |= ERR_ALERT | ERR_FATAL;
4320 goto out;
4321}
4322
4323/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004324int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004325 const char *file, int line)
4326{
4327 static char pgsql_req[] = {
4328 "%[var(check.plen),htonl,hex]" /* The packet length*/
4329 "00030000" /* the version 3.0 */
4330 "7573657200" /* "user" key */
4331 "%[var(check.username),hex]00" /* the username */
4332 "00"
4333 };
4334
4335 struct tcpcheck_ruleset *rs = NULL;
4336 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4337 struct tcpcheck_rule *chk;
4338 struct tcpcheck_var *var = NULL;
4339 char *user = NULL, *errmsg = NULL;
4340 size_t packetlen = 0;
4341 int err_code = 0;
4342
4343 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4344 err_code |= ERR_WARN;
4345
4346 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4347 goto out;
4348
4349 curpx->options2 &= ~PR_O2_CHK_ANY;
4350 curpx->options2 |= PR_O2_TCPCHK_CHK;
4351
4352 free_tcpcheck_vars(&rules->preset_vars);
4353 rules->list = NULL;
4354 rules->flags = 0;
4355
4356 cur_arg += 2;
4357 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4358 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4359 file, line, args[0], args[1]);
4360 goto error;
4361 }
4362 if (strcmp(args[cur_arg], "user") == 0) {
4363 packetlen = 15 + strlen(args[cur_arg+1]);
4364 user = strdup(args[cur_arg+1]);
4365
4366 var = create_tcpcheck_var(ist("check.username"));
4367 if (user == NULL || var == NULL) {
4368 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4369 goto error;
4370 }
4371 var->data.type = SMP_T_STR;
4372 var->data.u.str.area = user;
4373 var->data.u.str.data = strlen(user);
4374 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004375 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004376 user = NULL;
4377 var = NULL;
4378
4379 var = create_tcpcheck_var(ist("check.plen"));
4380 if (var == NULL) {
4381 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4382 goto error;
4383 }
4384 var->data.type = SMP_T_SINT;
4385 var->data.u.sint = packetlen;
4386 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004387 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004388 var = NULL;
4389 }
4390 else {
4391 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4392 file, line, args[0], args[1]);
4393 goto error;
4394 }
4395
4396 rs = find_tcpcheck_ruleset("*pgsql-check");
4397 if (rs)
4398 goto ruleset_found;
4399
4400 rs = create_tcpcheck_ruleset("*pgsql-check");
4401 if (rs == NULL) {
4402 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4403 goto error;
4404 }
4405
4406 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4407 1, curpx, &rs->rules, file, line, &errmsg);
4408 if (!chk) {
4409 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4410 goto error;
4411 }
4412 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004413 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004414
4415 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4416 1, curpx, &rs->rules, file, line, &errmsg);
4417 if (!chk) {
4418 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4419 goto error;
4420 }
4421 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004422 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004423
4424 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4425 "min-recv", "5",
4426 "error-status", "L7RSP",
4427 "on-error", "%[res.payload(6,0)]",
4428 ""},
4429 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4430 if (!chk) {
4431 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4432 goto error;
4433 }
4434 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004435 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004436
4437 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4438 "min-recv", "9",
4439 "error-status", "L7STS",
4440 "on-success", "PostgreSQL server is ok",
4441 "on-error", "PostgreSQL unknown error",
4442 ""},
4443 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4444 if (!chk) {
4445 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4446 goto error;
4447 }
4448 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004449 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004450
4451 ruleset_found:
4452 rules->list = &rs->rules;
4453 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4454 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4455
4456 out:
4457 free(errmsg);
4458 return err_code;
4459
4460 error:
4461 free(user);
4462 free(var);
4463 free_tcpcheck_vars(&rules->preset_vars);
4464 free_tcpcheck_ruleset(rs);
4465 err_code |= ERR_ALERT | ERR_FATAL;
4466 goto out;
4467}
4468
4469
4470/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004471int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004472 const char *file, int line)
4473{
4474 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4475 * const char mysql40_client_auth_pkt[] = {
4476 * "\x0e\x00\x00" // packet length
4477 * "\x01" // packet number
4478 * "\x00\x00" // client capabilities
4479 * "\x00\x00\x01" // max packet
4480 * "haproxy\x00" // username (null terminated string)
4481 * "\x00" // filler (always 0x00)
4482 * "\x01\x00\x00" // packet length
4483 * "\x00" // packet number
4484 * "\x01" // COM_QUIT command
4485 * };
4486 */
4487 static char mysql40_rsname[] = "*mysql40-check";
4488 static char mysql40_req[] = {
4489 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4490 "0080" /* client capabilities */
4491 "000001" /* max packet */
4492 "%[var(check.username),hex]00" /* the username */
4493 "00" /* filler (always 0x00) */
4494 "010000" /* packet length*/
4495 "00" /* sequence ID */
4496 "01" /* COM_QUIT command */
4497 };
4498
4499 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4500 * const char mysql41_client_auth_pkt[] = {
4501 * "\x0e\x00\x00\" // packet length
4502 * "\x01" // packet number
4503 * "\x00\x00\x00\x00" // client capabilities
4504 * "\x00\x00\x00\x01" // max packet
4505 * "\x21" // character set (UTF-8)
4506 * char[23] // All zeroes
4507 * "haproxy\x00" // username (null terminated string)
4508 * "\x00" // filler (always 0x00)
4509 * "\x01\x00\x00" // packet length
4510 * "\x00" // packet number
4511 * "\x01" // COM_QUIT command
4512 * };
4513 */
4514 static char mysql41_rsname[] = "*mysql41-check";
4515 static char mysql41_req[] = {
4516 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4517 "00820000" /* client capabilities */
4518 "00800001" /* max packet */
4519 "21" /* character set (UTF-8) */
4520 "000000000000000000000000" /* 23 bytes, al zeroes */
4521 "0000000000000000000000"
4522 "%[var(check.username),hex]00" /* the username */
4523 "00" /* filler (always 0x00) */
4524 "010000" /* packet length*/
4525 "00" /* sequence ID */
4526 "01" /* COM_QUIT command */
4527 };
4528
4529 struct tcpcheck_ruleset *rs = NULL;
4530 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4531 struct tcpcheck_rule *chk;
4532 struct tcpcheck_var *var = NULL;
4533 char *mysql_rsname = "*mysql-check";
4534 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4535 int index = 0, err_code = 0;
4536
4537 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4538 err_code |= ERR_WARN;
4539
4540 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4541 goto out;
4542
4543 curpx->options2 &= ~PR_O2_CHK_ANY;
4544 curpx->options2 |= PR_O2_TCPCHK_CHK;
4545
4546 free_tcpcheck_vars(&rules->preset_vars);
4547 rules->list = NULL;
4548 rules->flags = 0;
4549
4550 cur_arg += 2;
4551 if (*args[cur_arg]) {
4552 int packetlen, userlen;
4553
4554 if (strcmp(args[cur_arg], "user") != 0) {
4555 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4556 file, line, args[0], args[1], args[cur_arg]);
4557 goto error;
4558 }
4559
4560 if (*(args[cur_arg+1]) == 0) {
4561 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4562 file, line, args[0], args[1], args[cur_arg]);
4563 goto error;
4564 }
4565
4566 hdr = calloc(4, sizeof(*hdr));
4567 user = strdup(args[cur_arg+1]);
4568 userlen = strlen(args[cur_arg+1]);
4569
4570 if (hdr == NULL || user == NULL) {
4571 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4572 goto error;
4573 }
4574
4575 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4576 packetlen = userlen + 7 + 27;
4577 mysql_req = mysql41_req;
4578 mysql_rsname = mysql41_rsname;
4579 }
4580 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4581 packetlen = userlen + 7;
4582 mysql_req = mysql40_req;
4583 mysql_rsname = mysql40_rsname;
4584 }
4585 else {
4586 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4587 file, line, args[cur_arg], args[cur_arg+2]);
4588 goto error;
4589 }
4590
4591 hdr[0] = (unsigned char)(packetlen & 0xff);
4592 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4593 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4594 hdr[3] = 1;
4595
4596 var = create_tcpcheck_var(ist("check.header"));
4597 if (var == NULL) {
4598 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4599 goto error;
4600 }
4601 var->data.type = SMP_T_STR;
4602 var->data.u.str.area = hdr;
4603 var->data.u.str.data = 4;
4604 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004605 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004606 hdr = NULL;
4607 var = NULL;
4608
4609 var = create_tcpcheck_var(ist("check.username"));
4610 if (var == NULL) {
4611 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4612 goto error;
4613 }
4614 var->data.type = SMP_T_STR;
4615 var->data.u.str.area = user;
4616 var->data.u.str.data = strlen(user);
4617 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004618 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004619 user = NULL;
4620 var = NULL;
4621 }
4622
4623 rs = find_tcpcheck_ruleset(mysql_rsname);
4624 if (rs)
4625 goto ruleset_found;
4626
4627 rs = create_tcpcheck_ruleset(mysql_rsname);
4628 if (rs == NULL) {
4629 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4630 goto error;
4631 }
4632
4633 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4634 1, curpx, &rs->rules, file, line, &errmsg);
4635 if (!chk) {
4636 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4637 goto error;
4638 }
4639 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004640 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004641
4642 if (mysql_req) {
4643 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4644 1, curpx, &rs->rules, file, line, &errmsg);
4645 if (!chk) {
4646 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4647 goto error;
4648 }
4649 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004650 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004651 }
4652
4653 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4654 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4655 if (!chk) {
4656 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4657 goto error;
4658 }
4659 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4660 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004661 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004662
4663 if (mysql_req) {
4664 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4665 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4666 if (!chk) {
4667 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4668 goto error;
4669 }
4670 chk->expect.custom = tcpcheck_mysql_expect_ok;
4671 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004672 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004673 }
4674
4675 ruleset_found:
4676 rules->list = &rs->rules;
4677 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4678 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4679
4680 out:
4681 free(errmsg);
4682 return err_code;
4683
4684 error:
4685 free(hdr);
4686 free(user);
4687 free(var);
4688 free_tcpcheck_vars(&rules->preset_vars);
4689 free_tcpcheck_ruleset(rs);
4690 err_code |= ERR_ALERT | ERR_FATAL;
4691 goto out;
4692}
4693
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004694int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004695 const char *file, int line)
4696{
4697 static char *ldap_req = "300C020101600702010304008000";
4698
4699 struct tcpcheck_ruleset *rs = NULL;
4700 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4701 struct tcpcheck_rule *chk;
4702 char *errmsg = NULL;
4703 int err_code = 0;
4704
4705 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4706 err_code |= ERR_WARN;
4707
4708 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4709 goto out;
4710
4711 curpx->options2 &= ~PR_O2_CHK_ANY;
4712 curpx->options2 |= PR_O2_TCPCHK_CHK;
4713
4714 free_tcpcheck_vars(&rules->preset_vars);
4715 rules->list = NULL;
4716 rules->flags = 0;
4717
4718 rs = find_tcpcheck_ruleset("*ldap-check");
4719 if (rs)
4720 goto ruleset_found;
4721
4722 rs = create_tcpcheck_ruleset("*ldap-check");
4723 if (rs == NULL) {
4724 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4725 goto error;
4726 }
4727
4728 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4729 1, curpx, &rs->rules, file, line, &errmsg);
4730 if (!chk) {
4731 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4732 goto error;
4733 }
4734 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004735 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004736
4737 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4738 "min-recv", "14",
4739 "on-error", "Not LDAPv3 protocol",
4740 ""},
4741 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4742 if (!chk) {
4743 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4744 goto error;
4745 }
4746 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004747 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004748
4749 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4750 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4751 if (!chk) {
4752 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4753 goto error;
4754 }
4755 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4756 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004757 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004758
4759 ruleset_found:
4760 rules->list = &rs->rules;
4761 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4762 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4763
4764 out:
4765 free(errmsg);
4766 return err_code;
4767
4768 error:
4769 free_tcpcheck_ruleset(rs);
4770 err_code |= ERR_ALERT | ERR_FATAL;
4771 goto out;
4772}
4773
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004774int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004775 const char *file, int line)
4776{
4777 struct tcpcheck_ruleset *rs = NULL;
4778 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4779 struct tcpcheck_rule *chk;
4780 char *spop_req = NULL;
4781 char *errmsg = NULL;
4782 int spop_len = 0, err_code = 0;
4783
4784 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4785 err_code |= ERR_WARN;
4786
4787 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4788 goto out;
4789
4790 curpx->options2 &= ~PR_O2_CHK_ANY;
4791 curpx->options2 |= PR_O2_TCPCHK_CHK;
4792
4793 free_tcpcheck_vars(&rules->preset_vars);
4794 rules->list = NULL;
4795 rules->flags = 0;
4796
4797
4798 rs = find_tcpcheck_ruleset("*spop-check");
4799 if (rs)
4800 goto ruleset_found;
4801
4802 rs = create_tcpcheck_ruleset("*spop-check");
4803 if (rs == NULL) {
4804 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4805 goto error;
4806 }
4807
4808 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4809 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4810 goto error;
4811 }
4812 chunk_reset(&trash);
4813 dump_binary(&trash, spop_req, spop_len);
4814 trash.area[trash.data] = '\0';
4815
4816 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4817 1, curpx, &rs->rules, file, line, &errmsg);
4818 if (!chk) {
4819 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4820 goto error;
4821 }
4822 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004823 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004824
4825 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4826 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4827 if (!chk) {
4828 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4829 goto error;
4830 }
4831 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4832 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004833 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004834
4835 ruleset_found:
4836 rules->list = &rs->rules;
4837 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4838 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4839
4840 out:
4841 free(spop_req);
4842 free(errmsg);
4843 return err_code;
4844
4845 error:
4846 free_tcpcheck_ruleset(rs);
4847 err_code |= ERR_ALERT | ERR_FATAL;
4848 goto out;
4849}
4850
4851
4852static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4853{
4854 struct tcpcheck_rule *chk = NULL;
4855 struct tcpcheck_http_hdr *hdr = NULL;
4856 char *meth = NULL, *uri = NULL, *vsn = NULL;
4857 char *hdrs, *body;
4858
4859 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4860 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4861 if (hdrs == body)
4862 hdrs = NULL;
4863 if (hdrs) {
4864 *hdrs = '\0';
4865 hdrs +=2;
4866 }
4867 if (body) {
4868 *body = '\0';
4869 body += 4;
4870 }
4871 if (hdrs || body) {
4872 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4873 " Please, consider to use 'http-check send' directive instead.");
4874 }
4875
4876 chk = calloc(1, sizeof(*chk));
4877 if (!chk) {
4878 memprintf(errmsg, "out of memory");
4879 goto error;
4880 }
4881 chk->action = TCPCHK_ACT_SEND;
4882 chk->send.type = TCPCHK_SEND_HTTP;
4883 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4884 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4885 LIST_INIT(&chk->send.http.hdrs);
4886
4887 /* Copy the method, uri and version */
4888 if (*args[cur_arg]) {
4889 if (!*args[cur_arg+1])
4890 uri = args[cur_arg];
4891 else
4892 meth = args[cur_arg];
4893 }
4894 if (*args[cur_arg+1])
4895 uri = args[cur_arg+1];
4896 if (*args[cur_arg+2])
4897 vsn = args[cur_arg+2];
4898
4899 if (meth) {
4900 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4901 chk->send.http.meth.str.area = strdup(meth);
4902 chk->send.http.meth.str.data = strlen(meth);
4903 if (!chk->send.http.meth.str.area) {
4904 memprintf(errmsg, "out of memory");
4905 goto error;
4906 }
4907 }
4908 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004909 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004910 if (!isttest(chk->send.http.uri)) {
4911 memprintf(errmsg, "out of memory");
4912 goto error;
4913 }
4914 }
4915 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004916 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004917 if (!isttest(chk->send.http.vsn)) {
4918 memprintf(errmsg, "out of memory");
4919 goto error;
4920 }
4921 }
4922
4923 /* Copy the header */
4924 if (hdrs) {
4925 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4926 struct h1m h1m;
4927 int i, ret;
4928
4929 /* Build and parse the request */
4930 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4931
4932 h1m.flags = H1_MF_HDRS_ONLY;
4933 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4934 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4935 &h1m, NULL);
4936 if (ret <= 0) {
4937 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4938 goto error;
4939 }
4940
4941 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4942 hdr = calloc(1, sizeof(*hdr));
4943 if (!hdr) {
4944 memprintf(errmsg, "out of memory");
4945 goto error;
4946 }
4947 LIST_INIT(&hdr->value);
4948 hdr->name = istdup(tmp_hdrs[i].n);
4949 if (!hdr->name.ptr) {
4950 memprintf(errmsg, "out of memory");
4951 goto error;
4952 }
4953
4954 ist0(tmp_hdrs[i].v);
4955 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
4956 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02004957 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004958 }
4959 }
4960
4961 /* Copy the body */
4962 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004963 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004964 if (!isttest(chk->send.http.body)) {
4965 memprintf(errmsg, "out of memory");
4966 goto error;
4967 }
4968 }
4969
4970 return chk;
4971
4972 error:
4973 free_tcpcheck_http_hdr(hdr);
4974 free_tcpcheck(chk, 0);
4975 return NULL;
4976}
4977
4978/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004979int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004980 const char *file, int line)
4981{
4982 struct tcpcheck_ruleset *rs = NULL;
4983 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4984 struct tcpcheck_rule *chk;
4985 char *errmsg = NULL;
4986 int err_code = 0;
4987
4988 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4989 err_code |= ERR_WARN;
4990
4991 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4992 goto out;
4993
4994 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
4995 if (!chk) {
4996 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
4997 goto error;
4998 }
4999 if (errmsg) {
5000 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5001 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005002 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005003 }
5004
5005 no_request:
5006 curpx->options2 &= ~PR_O2_CHK_ANY;
5007 curpx->options2 |= PR_O2_TCPCHK_CHK;
5008
5009 free_tcpcheck_vars(&rules->preset_vars);
5010 rules->list = NULL;
5011 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5012
5013 /* Deduce the ruleset name from the proxy info */
5014 chunk_printf(&trash, "*http-check-%s_%s-%d",
5015 ((curpx == defpx) ? "defaults" : curpx->id),
5016 curpx->conf.file, curpx->conf.line);
5017
5018 rs = find_tcpcheck_ruleset(b_orig(&trash));
5019 if (rs == NULL) {
5020 rs = create_tcpcheck_ruleset(b_orig(&trash));
5021 if (rs == NULL) {
5022 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5023 goto error;
5024 }
5025 }
5026
5027 rules->list = &rs->rules;
5028 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5029 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5030 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5031 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5032 rules->list = NULL;
5033 goto error;
5034 }
5035
5036 out:
5037 free(errmsg);
5038 return err_code;
5039
5040 error:
5041 free_tcpcheck_ruleset(rs);
5042 free_tcpcheck(chk, 0);
5043 err_code |= ERR_ALERT | ERR_FATAL;
5044 goto out;
5045}
5046
5047/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005048int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005049 const char *file, int line)
5050{
5051 struct tcpcheck_ruleset *rs = NULL;
5052 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5053 int err_code = 0;
5054
5055 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5056 err_code |= ERR_WARN;
5057
5058 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5059 goto out;
5060
5061 curpx->options2 &= ~PR_O2_CHK_ANY;
5062 curpx->options2 |= PR_O2_TCPCHK_CHK;
5063
5064 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5065 /* If a tcp-check rulesset is already set, do nothing */
5066 if (rules->list)
5067 goto out;
5068
5069 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5070 * get it.
5071 */
5072 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5073 goto curpx_ruleset;
5074
5075 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5076 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5077 rs = find_tcpcheck_ruleset(b_orig(&trash));
5078 if (rs)
5079 goto ruleset_found;
5080 }
5081
5082 curpx_ruleset:
5083 /* Deduce the ruleset name from the proxy info */
5084 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5085 ((curpx == defpx) ? "defaults" : curpx->id),
5086 curpx->conf.file, curpx->conf.line);
5087
5088 rs = find_tcpcheck_ruleset(b_orig(&trash));
5089 if (rs == NULL) {
5090 rs = create_tcpcheck_ruleset(b_orig(&trash));
5091 if (rs == NULL) {
5092 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5093 goto error;
5094 }
5095 }
5096
5097 ruleset_found:
5098 free_tcpcheck_vars(&rules->preset_vars);
5099 rules->list = &rs->rules;
5100 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5101 rules->flags |= TCPCHK_RULES_TCP_CHK;
5102
5103 out:
5104 return err_code;
5105
5106 error:
5107 err_code |= ERR_ALERT | ERR_FATAL;
5108 goto out;
5109}
5110
Willy Tarreau51cd5952020-06-05 12:25:38 +02005111static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005112 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005113 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5114 { 0, NULL, NULL },
5115}};
5116
5117REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5118REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5119REGISTER_POST_DEINIT(deinit_tcpchecks);
5120INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);