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