blob: 90f1ee953ad7ffc03e44831c838cce26c02cfad5 [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
3621 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
3622 return 0;
3623 memset(tcpcheck, 0, sizeof(*tcpcheck));
3624 tcpcheck->action = TCPCHK_ACT_EXPECT;
3625
3626 expect = &tcpcheck->expect;
3627 expect->type = TCPCHK_EXPECT_STRING;
3628 LIST_INIT(&expect->onerror_fmt);
3629 LIST_INIT(&expect->onsuccess_fmt);
3630 expect->ok_status = HCHK_STATUS_L7OKD;
3631 expect->err_status = HCHK_STATUS_L7RSP;
3632 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003633 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003634 if (!isttest(expect->data)) {
3635 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3636 return 0;
3637 }
3638
3639 /* All tcp-check expect points back to the first inverse expect rule
3640 * in a chain of one or more expect rule, potentially itself.
3641 */
3642 tcpcheck->expect.head = tcpcheck;
3643 list_for_each_entry_rev(prev_check, rules->list, list) {
3644 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3645 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3646 tcpcheck->expect.head = prev_check;
3647 continue;
3648 }
3649 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3650 break;
3651 }
3652 LIST_ADDQ(rules->list, &tcpcheck->list);
3653 return 1;
3654}
3655
3656int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3657{
3658 struct tcpcheck_rule *tcpcheck;
3659 struct tcpcheck_send *send;
3660 const char *in;
3661 char *dst;
3662 int i;
3663
3664 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
3665 return 0;
3666 memset(tcpcheck, 0, sizeof(*tcpcheck));
3667 tcpcheck->action = TCPCHK_ACT_SEND;
3668
3669 send = &tcpcheck->send;
3670 send->type = TCPCHK_SEND_STRING;
3671
3672 for (i = 0; strs[i]; i++)
3673 send->data.len += strlen(strs[i]);
3674
3675 send->data.ptr = malloc(istlen(send->data) + 1);
3676 if (!isttest(send->data)) {
3677 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3678 return 0;
3679 }
3680
3681 dst = istptr(send->data);
3682 for (i = 0; strs[i]; i++)
3683 for (in = strs[i]; (*dst = *in++); dst++);
3684 *dst = 0;
3685
3686 LIST_ADDQ(rules->list, &tcpcheck->list);
3687 return 1;
3688}
3689
3690/* Parses the "tcp-check" proxy keyword */
3691static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003692 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003693 char **errmsg)
3694{
3695 struct tcpcheck_ruleset *rs = NULL;
3696 struct tcpcheck_rule *chk = NULL;
3697 int index, cur_arg, ret = 0;
3698
3699 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3700 ret = 1;
3701
3702 /* Deduce the ruleset name from the proxy info */
3703 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3704 ((curpx == defpx) ? "defaults" : curpx->id),
3705 curpx->conf.file, curpx->conf.line);
3706
3707 rs = find_tcpcheck_ruleset(b_orig(&trash));
3708 if (rs == NULL) {
3709 rs = create_tcpcheck_ruleset(b_orig(&trash));
3710 if (rs == NULL) {
3711 memprintf(errmsg, "out of memory.\n");
3712 goto error;
3713 }
3714 }
3715
3716 index = 0;
3717 if (!LIST_ISEMPTY(&rs->rules)) {
3718 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3719 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003720 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003721 }
3722
3723 cur_arg = 1;
3724 if (strcmp(args[cur_arg], "connect") == 0)
3725 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3726 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3727 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3728 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3729 else if (strcmp(args[cur_arg], "expect") == 0)
3730 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3731 else if (strcmp(args[cur_arg], "comment") == 0)
3732 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3733 else {
3734 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3735
3736 if (!kw) {
3737 action_kw_tcp_check_build_list(&trash);
3738 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3739 "%s%s. but got '%s'",
3740 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3741 goto error;
3742 }
3743 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3744 }
3745
3746 if (!chk) {
3747 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3748 goto error;
3749 }
3750 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3751
3752 /* No error: add the tcp-check rule in the list */
3753 chk->index = index;
3754 LIST_ADDQ(&rs->rules, &chk->list);
3755
3756 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3757 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3758 /* Use this ruleset if the proxy already has tcp-check enabled */
3759 curpx->tcpcheck_rules.list = &rs->rules;
3760 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3761 }
3762 else {
3763 /* mark this ruleset as unused for now */
3764 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3765 }
3766
3767 return ret;
3768
3769 error:
3770 free_tcpcheck(chk, 0);
3771 free_tcpcheck_ruleset(rs);
3772 return -1;
3773}
3774
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003775/* Parses the "http-check" proxy keyword */
3776static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003777 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003778 char **errmsg)
3779{
3780 struct tcpcheck_ruleset *rs = NULL;
3781 struct tcpcheck_rule *chk = NULL;
3782 int index, cur_arg, ret = 0;
3783
3784 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3785 ret = 1;
3786
3787 cur_arg = 1;
3788 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3789 /* enable a graceful server shutdown on an HTTP 404 response */
3790 curpx->options |= PR_O_DISABLE404;
3791 if (too_many_args(1, args, errmsg, NULL))
3792 goto error;
3793 goto out;
3794 }
3795 else if (strcmp(args[cur_arg], "send-state") == 0) {
3796 /* enable emission of the apparent state of a server in HTTP checks */
3797 curpx->options2 |= PR_O2_CHK_SNDST;
3798 if (too_many_args(1, args, errmsg, NULL))
3799 goto error;
3800 goto out;
3801 }
3802
3803 /* Deduce the ruleset name from the proxy info */
3804 chunk_printf(&trash, "*http-check-%s_%s-%d",
3805 ((curpx == defpx) ? "defaults" : curpx->id),
3806 curpx->conf.file, curpx->conf.line);
3807
3808 rs = find_tcpcheck_ruleset(b_orig(&trash));
3809 if (rs == NULL) {
3810 rs = create_tcpcheck_ruleset(b_orig(&trash));
3811 if (rs == NULL) {
3812 memprintf(errmsg, "out of memory.\n");
3813 goto error;
3814 }
3815 }
3816
3817 index = 0;
3818 if (!LIST_ISEMPTY(&rs->rules)) {
3819 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3820 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
3821 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003822 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003823 }
3824
3825 if (strcmp(args[cur_arg], "connect") == 0)
3826 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3827 else if (strcmp(args[cur_arg], "send") == 0)
3828 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3829 else if (strcmp(args[cur_arg], "expect") == 0)
3830 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
3831 file, line, errmsg);
3832 else if (strcmp(args[cur_arg], "comment") == 0)
3833 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3834 else {
3835 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3836
3837 if (!kw) {
3838 action_kw_tcp_check_build_list(&trash);
3839 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
3840 " 'send', 'expect'%s%s. but got '%s'",
3841 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3842 goto error;
3843 }
3844 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3845 }
3846
3847 if (!chk) {
3848 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3849 goto error;
3850 }
3851 ret = (*errmsg != NULL); /* Handle warning */
3852
3853 chk->index = index;
3854 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3855 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3856 /* Use this ruleset if the proxy already has http-check enabled */
3857 curpx->tcpcheck_rules.list = &rs->rules;
3858 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
3859 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
3860 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3861 curpx->tcpcheck_rules.list = NULL;
3862 goto error;
3863 }
3864 }
3865 else {
3866 /* mark this ruleset as unused for now */
3867 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
3868 LIST_ADDQ(&rs->rules, &chk->list);
3869 }
3870
3871 out:
3872 return ret;
3873
3874 error:
3875 free_tcpcheck(chk, 0);
3876 free_tcpcheck_ruleset(rs);
3877 return -1;
3878}
3879
3880/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01003881int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003882 const char *file, int line)
3883{
3884 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
3885 static char *redis_res = "+PONG\r\n";
3886
3887 struct tcpcheck_ruleset *rs = NULL;
3888 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
3889 struct tcpcheck_rule *chk;
3890 char *errmsg = NULL;
3891 int err_code = 0;
3892
3893 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
3894 err_code |= ERR_WARN;
3895
3896 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
3897 goto out;
3898
3899 curpx->options2 &= ~PR_O2_CHK_ANY;
3900 curpx->options2 |= PR_O2_TCPCHK_CHK;
3901
3902 free_tcpcheck_vars(&rules->preset_vars);
3903 rules->list = NULL;
3904 rules->flags = 0;
3905
3906 rs = find_tcpcheck_ruleset("*redis-check");
3907 if (rs)
3908 goto ruleset_found;
3909
3910 rs = create_tcpcheck_ruleset("*redis-check");
3911 if (rs == NULL) {
3912 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
3913 goto error;
3914 }
3915
3916 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
3917 1, curpx, &rs->rules, file, line, &errmsg);
3918 if (!chk) {
3919 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
3920 goto error;
3921 }
3922 chk->index = 0;
3923 LIST_ADDQ(&rs->rules, &chk->list);
3924
3925 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
3926 "error-status", "L7STS",
3927 "on-error", "%[res.payload(0,0),cut_crlf]",
3928 "on-success", "Redis server is ok",
3929 ""},
3930 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
3931 if (!chk) {
3932 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
3933 goto error;
3934 }
3935 chk->index = 1;
3936 LIST_ADDQ(&rs->rules, &chk->list);
3937
3938 ruleset_found:
3939 rules->list = &rs->rules;
3940 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
3941 rules->flags |= TCPCHK_RULES_REDIS_CHK;
3942
3943 out:
3944 free(errmsg);
3945 return err_code;
3946
3947 error:
3948 free_tcpcheck_ruleset(rs);
3949 err_code |= ERR_ALERT | ERR_FATAL;
3950 goto out;
3951}
3952
3953
3954/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01003955int 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 +01003956 const char *file, int line)
3957{
3958 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
3959 * ssl-hello-chk option to ensure that the remote server speaks SSL.
3960 *
3961 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
3962 */
3963 static char sslv3_client_hello[] = {
3964 "16" /* ContentType : 0x16 = Handshake */
3965 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
3966 "0079" /* ContentLength : 0x79 bytes after this one */
3967 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
3968 "000075" /* HandshakeLength : 0x75 bytes after this one */
3969 "0300" /* Hello Version : 0x0300 = v3 */
3970 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
3971 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
3972 "00" /* Session ID length : empty (no session ID) */
3973 "004E" /* Cipher Suite Length : 78 bytes after this one */
3974 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
3975 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
3976 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
3977 "000D" "000E" "000F" "0010" /* various bit lengths, */
3978 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
3979 "0015" "0016" "0017" "0018"
3980 "0019" "001A" "001B" "002F"
3981 "0030" "0031" "0032" "0033"
3982 "0034" "0035" "0036" "0037"
3983 "0038" "0039" "003A"
3984 "01" /* Compression Length : 0x01 = 1 byte for types */
3985 "00" /* Compression Type : 0x00 = NULL compression */
3986 };
3987
3988 struct tcpcheck_ruleset *rs = NULL;
3989 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
3990 struct tcpcheck_rule *chk;
3991 char *errmsg = NULL;
3992 int err_code = 0;
3993
3994 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
3995 err_code |= ERR_WARN;
3996
3997 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
3998 goto out;
3999
4000 curpx->options2 &= ~PR_O2_CHK_ANY;
4001 curpx->options2 |= PR_O2_TCPCHK_CHK;
4002
4003 free_tcpcheck_vars(&rules->preset_vars);
4004 rules->list = NULL;
4005 rules->flags = 0;
4006
4007 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4008 if (rs)
4009 goto ruleset_found;
4010
4011 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4012 if (rs == NULL) {
4013 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4014 goto error;
4015 }
4016
4017 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4018 1, curpx, &rs->rules, file, line, &errmsg);
4019 if (!chk) {
4020 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4021 goto error;
4022 }
4023 chk->index = 0;
4024 LIST_ADDQ(&rs->rules, &chk->list);
4025
4026 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4027 "min-recv", "5", "ok-status", "L6OK",
4028 "error-status", "L6RSP", "tout-status", "L6TOUT",
4029 ""},
4030 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4031 if (!chk) {
4032 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4033 goto error;
4034 }
4035 chk->index = 1;
4036 LIST_ADDQ(&rs->rules, &chk->list);
4037
4038 ruleset_found:
4039 rules->list = &rs->rules;
4040 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4041 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4042
4043 out:
4044 free(errmsg);
4045 return err_code;
4046
4047 error:
4048 free_tcpcheck_ruleset(rs);
4049 err_code |= ERR_ALERT | ERR_FATAL;
4050 goto out;
4051}
4052
4053/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004054int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004055 const char *file, int line)
4056{
4057 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4058
4059 struct tcpcheck_ruleset *rs = NULL;
4060 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4061 struct tcpcheck_rule *chk;
4062 struct tcpcheck_var *var = NULL;
4063 char *cmd = NULL, *errmsg = NULL;
4064 int err_code = 0;
4065
4066 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4067 err_code |= ERR_WARN;
4068
4069 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4070 goto out;
4071
4072 curpx->options2 &= ~PR_O2_CHK_ANY;
4073 curpx->options2 |= PR_O2_TCPCHK_CHK;
4074
4075 free_tcpcheck_vars(&rules->preset_vars);
4076 rules->list = NULL;
4077 rules->flags = 0;
4078
4079 cur_arg += 2;
4080 if (*args[cur_arg] && *args[cur_arg+1] &&
4081 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4082 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4083 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4084 if (cmd)
4085 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4086 }
4087 else {
4088 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4089 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4090 cmd = strdup("HELO localhost");
4091 }
4092
4093 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4094 if (cmd == NULL || var == NULL) {
4095 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4096 goto error;
4097 }
4098 var->data.type = SMP_T_STR;
4099 var->data.u.str.area = cmd;
4100 var->data.u.str.data = strlen(cmd);
4101 LIST_INIT(&var->list);
4102 LIST_ADDQ(&rules->preset_vars, &var->list);
4103 cmd = NULL;
4104 var = NULL;
4105
4106 rs = find_tcpcheck_ruleset("*smtp-check");
4107 if (rs)
4108 goto ruleset_found;
4109
4110 rs = create_tcpcheck_ruleset("*smtp-check");
4111 if (rs == NULL) {
4112 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4113 goto error;
4114 }
4115
4116 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4117 1, curpx, &rs->rules, file, line, &errmsg);
4118 if (!chk) {
4119 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4120 goto error;
4121 }
4122 chk->index = 0;
4123 LIST_ADDQ(&rs->rules, &chk->list);
4124
4125 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4126 "min-recv", "4",
4127 "error-status", "L7RSP",
4128 "on-error", "%[res.payload(0,0),cut_crlf]",
4129 ""},
4130 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4131 if (!chk) {
4132 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4133 goto error;
4134 }
4135 chk->index = 1;
4136 LIST_ADDQ(&rs->rules, &chk->list);
4137
4138 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4139 "min-recv", "4",
4140 "error-status", "L7STS",
4141 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4142 "status-code", "res.payload(0,3)",
4143 ""},
4144 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4145 if (!chk) {
4146 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4147 goto error;
4148 }
4149 chk->index = 2;
4150 LIST_ADDQ(&rs->rules, &chk->list);
4151
4152 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4153 1, curpx, &rs->rules, file, line, &errmsg);
4154 if (!chk) {
4155 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4156 goto error;
4157 }
4158 chk->index = 3;
4159 LIST_ADDQ(&rs->rules, &chk->list);
4160
4161 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4162 "min-recv", "4",
4163 "error-status", "L7STS",
4164 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4165 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4166 "status-code", "res.payload(0,3)",
4167 ""},
4168 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4169 if (!chk) {
4170 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4171 goto error;
4172 }
4173 chk->index = 4;
4174 LIST_ADDQ(&rs->rules, &chk->list);
4175
4176 ruleset_found:
4177 rules->list = &rs->rules;
4178 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4179 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4180
4181 out:
4182 free(errmsg);
4183 return err_code;
4184
4185 error:
4186 free(cmd);
4187 free(var);
4188 free_tcpcheck_vars(&rules->preset_vars);
4189 free_tcpcheck_ruleset(rs);
4190 err_code |= ERR_ALERT | ERR_FATAL;
4191 goto out;
4192}
4193
4194/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004195int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004196 const char *file, int line)
4197{
4198 static char pgsql_req[] = {
4199 "%[var(check.plen),htonl,hex]" /* The packet length*/
4200 "00030000" /* the version 3.0 */
4201 "7573657200" /* "user" key */
4202 "%[var(check.username),hex]00" /* the username */
4203 "00"
4204 };
4205
4206 struct tcpcheck_ruleset *rs = NULL;
4207 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4208 struct tcpcheck_rule *chk;
4209 struct tcpcheck_var *var = NULL;
4210 char *user = NULL, *errmsg = NULL;
4211 size_t packetlen = 0;
4212 int err_code = 0;
4213
4214 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4215 err_code |= ERR_WARN;
4216
4217 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4218 goto out;
4219
4220 curpx->options2 &= ~PR_O2_CHK_ANY;
4221 curpx->options2 |= PR_O2_TCPCHK_CHK;
4222
4223 free_tcpcheck_vars(&rules->preset_vars);
4224 rules->list = NULL;
4225 rules->flags = 0;
4226
4227 cur_arg += 2;
4228 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4229 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4230 file, line, args[0], args[1]);
4231 goto error;
4232 }
4233 if (strcmp(args[cur_arg], "user") == 0) {
4234 packetlen = 15 + strlen(args[cur_arg+1]);
4235 user = strdup(args[cur_arg+1]);
4236
4237 var = create_tcpcheck_var(ist("check.username"));
4238 if (user == NULL || var == NULL) {
4239 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4240 goto error;
4241 }
4242 var->data.type = SMP_T_STR;
4243 var->data.u.str.area = user;
4244 var->data.u.str.data = strlen(user);
4245 LIST_INIT(&var->list);
4246 LIST_ADDQ(&rules->preset_vars, &var->list);
4247 user = NULL;
4248 var = NULL;
4249
4250 var = create_tcpcheck_var(ist("check.plen"));
4251 if (var == NULL) {
4252 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4253 goto error;
4254 }
4255 var->data.type = SMP_T_SINT;
4256 var->data.u.sint = packetlen;
4257 LIST_INIT(&var->list);
4258 LIST_ADDQ(&rules->preset_vars, &var->list);
4259 var = NULL;
4260 }
4261 else {
4262 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4263 file, line, args[0], args[1]);
4264 goto error;
4265 }
4266
4267 rs = find_tcpcheck_ruleset("*pgsql-check");
4268 if (rs)
4269 goto ruleset_found;
4270
4271 rs = create_tcpcheck_ruleset("*pgsql-check");
4272 if (rs == NULL) {
4273 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4274 goto error;
4275 }
4276
4277 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4278 1, curpx, &rs->rules, file, line, &errmsg);
4279 if (!chk) {
4280 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4281 goto error;
4282 }
4283 chk->index = 0;
4284 LIST_ADDQ(&rs->rules, &chk->list);
4285
4286 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4287 1, curpx, &rs->rules, file, line, &errmsg);
4288 if (!chk) {
4289 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4290 goto error;
4291 }
4292 chk->index = 1;
4293 LIST_ADDQ(&rs->rules, &chk->list);
4294
4295 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4296 "min-recv", "5",
4297 "error-status", "L7RSP",
4298 "on-error", "%[res.payload(6,0)]",
4299 ""},
4300 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4301 if (!chk) {
4302 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4303 goto error;
4304 }
4305 chk->index = 2;
4306 LIST_ADDQ(&rs->rules, &chk->list);
4307
4308 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4309 "min-recv", "9",
4310 "error-status", "L7STS",
4311 "on-success", "PostgreSQL server is ok",
4312 "on-error", "PostgreSQL unknown error",
4313 ""},
4314 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4315 if (!chk) {
4316 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4317 goto error;
4318 }
4319 chk->index = 3;
4320 LIST_ADDQ(&rs->rules, &chk->list);
4321
4322 ruleset_found:
4323 rules->list = &rs->rules;
4324 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4325 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4326
4327 out:
4328 free(errmsg);
4329 return err_code;
4330
4331 error:
4332 free(user);
4333 free(var);
4334 free_tcpcheck_vars(&rules->preset_vars);
4335 free_tcpcheck_ruleset(rs);
4336 err_code |= ERR_ALERT | ERR_FATAL;
4337 goto out;
4338}
4339
4340
4341/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004342int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004343 const char *file, int line)
4344{
4345 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4346 * const char mysql40_client_auth_pkt[] = {
4347 * "\x0e\x00\x00" // packet length
4348 * "\x01" // packet number
4349 * "\x00\x00" // client capabilities
4350 * "\x00\x00\x01" // max packet
4351 * "haproxy\x00" // username (null terminated string)
4352 * "\x00" // filler (always 0x00)
4353 * "\x01\x00\x00" // packet length
4354 * "\x00" // packet number
4355 * "\x01" // COM_QUIT command
4356 * };
4357 */
4358 static char mysql40_rsname[] = "*mysql40-check";
4359 static char mysql40_req[] = {
4360 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4361 "0080" /* client capabilities */
4362 "000001" /* max packet */
4363 "%[var(check.username),hex]00" /* the username */
4364 "00" /* filler (always 0x00) */
4365 "010000" /* packet length*/
4366 "00" /* sequence ID */
4367 "01" /* COM_QUIT command */
4368 };
4369
4370 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4371 * const char mysql41_client_auth_pkt[] = {
4372 * "\x0e\x00\x00\" // packet length
4373 * "\x01" // packet number
4374 * "\x00\x00\x00\x00" // client capabilities
4375 * "\x00\x00\x00\x01" // max packet
4376 * "\x21" // character set (UTF-8)
4377 * char[23] // All zeroes
4378 * "haproxy\x00" // username (null terminated string)
4379 * "\x00" // filler (always 0x00)
4380 * "\x01\x00\x00" // packet length
4381 * "\x00" // packet number
4382 * "\x01" // COM_QUIT command
4383 * };
4384 */
4385 static char mysql41_rsname[] = "*mysql41-check";
4386 static char mysql41_req[] = {
4387 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4388 "00820000" /* client capabilities */
4389 "00800001" /* max packet */
4390 "21" /* character set (UTF-8) */
4391 "000000000000000000000000" /* 23 bytes, al zeroes */
4392 "0000000000000000000000"
4393 "%[var(check.username),hex]00" /* the username */
4394 "00" /* filler (always 0x00) */
4395 "010000" /* packet length*/
4396 "00" /* sequence ID */
4397 "01" /* COM_QUIT command */
4398 };
4399
4400 struct tcpcheck_ruleset *rs = NULL;
4401 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4402 struct tcpcheck_rule *chk;
4403 struct tcpcheck_var *var = NULL;
4404 char *mysql_rsname = "*mysql-check";
4405 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4406 int index = 0, err_code = 0;
4407
4408 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4409 err_code |= ERR_WARN;
4410
4411 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4412 goto out;
4413
4414 curpx->options2 &= ~PR_O2_CHK_ANY;
4415 curpx->options2 |= PR_O2_TCPCHK_CHK;
4416
4417 free_tcpcheck_vars(&rules->preset_vars);
4418 rules->list = NULL;
4419 rules->flags = 0;
4420
4421 cur_arg += 2;
4422 if (*args[cur_arg]) {
4423 int packetlen, userlen;
4424
4425 if (strcmp(args[cur_arg], "user") != 0) {
4426 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4427 file, line, args[0], args[1], args[cur_arg]);
4428 goto error;
4429 }
4430
4431 if (*(args[cur_arg+1]) == 0) {
4432 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4433 file, line, args[0], args[1], args[cur_arg]);
4434 goto error;
4435 }
4436
4437 hdr = calloc(4, sizeof(*hdr));
4438 user = strdup(args[cur_arg+1]);
4439 userlen = strlen(args[cur_arg+1]);
4440
4441 if (hdr == NULL || user == NULL) {
4442 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4443 goto error;
4444 }
4445
4446 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4447 packetlen = userlen + 7 + 27;
4448 mysql_req = mysql41_req;
4449 mysql_rsname = mysql41_rsname;
4450 }
4451 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4452 packetlen = userlen + 7;
4453 mysql_req = mysql40_req;
4454 mysql_rsname = mysql40_rsname;
4455 }
4456 else {
4457 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4458 file, line, args[cur_arg], args[cur_arg+2]);
4459 goto error;
4460 }
4461
4462 hdr[0] = (unsigned char)(packetlen & 0xff);
4463 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4464 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4465 hdr[3] = 1;
4466
4467 var = create_tcpcheck_var(ist("check.header"));
4468 if (var == NULL) {
4469 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4470 goto error;
4471 }
4472 var->data.type = SMP_T_STR;
4473 var->data.u.str.area = hdr;
4474 var->data.u.str.data = 4;
4475 LIST_INIT(&var->list);
4476 LIST_ADDQ(&rules->preset_vars, &var->list);
4477 hdr = NULL;
4478 var = NULL;
4479
4480 var = create_tcpcheck_var(ist("check.username"));
4481 if (var == NULL) {
4482 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4483 goto error;
4484 }
4485 var->data.type = SMP_T_STR;
4486 var->data.u.str.area = user;
4487 var->data.u.str.data = strlen(user);
4488 LIST_INIT(&var->list);
4489 LIST_ADDQ(&rules->preset_vars, &var->list);
4490 user = NULL;
4491 var = NULL;
4492 }
4493
4494 rs = find_tcpcheck_ruleset(mysql_rsname);
4495 if (rs)
4496 goto ruleset_found;
4497
4498 rs = create_tcpcheck_ruleset(mysql_rsname);
4499 if (rs == NULL) {
4500 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4501 goto error;
4502 }
4503
4504 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4505 1, curpx, &rs->rules, file, line, &errmsg);
4506 if (!chk) {
4507 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4508 goto error;
4509 }
4510 chk->index = index++;
4511 LIST_ADDQ(&rs->rules, &chk->list);
4512
4513 if (mysql_req) {
4514 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4515 1, curpx, &rs->rules, file, line, &errmsg);
4516 if (!chk) {
4517 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4518 goto error;
4519 }
4520 chk->index = index++;
4521 LIST_ADDQ(&rs->rules, &chk->list);
4522 }
4523
4524 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4525 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4526 if (!chk) {
4527 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4528 goto error;
4529 }
4530 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4531 chk->index = index++;
4532 LIST_ADDQ(&rs->rules, &chk->list);
4533
4534 if (mysql_req) {
4535 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4536 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4537 if (!chk) {
4538 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4539 goto error;
4540 }
4541 chk->expect.custom = tcpcheck_mysql_expect_ok;
4542 chk->index = index++;
4543 LIST_ADDQ(&rs->rules, &chk->list);
4544 }
4545
4546 ruleset_found:
4547 rules->list = &rs->rules;
4548 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4549 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4550
4551 out:
4552 free(errmsg);
4553 return err_code;
4554
4555 error:
4556 free(hdr);
4557 free(user);
4558 free(var);
4559 free_tcpcheck_vars(&rules->preset_vars);
4560 free_tcpcheck_ruleset(rs);
4561 err_code |= ERR_ALERT | ERR_FATAL;
4562 goto out;
4563}
4564
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004565int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004566 const char *file, int line)
4567{
4568 static char *ldap_req = "300C020101600702010304008000";
4569
4570 struct tcpcheck_ruleset *rs = NULL;
4571 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4572 struct tcpcheck_rule *chk;
4573 char *errmsg = NULL;
4574 int err_code = 0;
4575
4576 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4577 err_code |= ERR_WARN;
4578
4579 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4580 goto out;
4581
4582 curpx->options2 &= ~PR_O2_CHK_ANY;
4583 curpx->options2 |= PR_O2_TCPCHK_CHK;
4584
4585 free_tcpcheck_vars(&rules->preset_vars);
4586 rules->list = NULL;
4587 rules->flags = 0;
4588
4589 rs = find_tcpcheck_ruleset("*ldap-check");
4590 if (rs)
4591 goto ruleset_found;
4592
4593 rs = create_tcpcheck_ruleset("*ldap-check");
4594 if (rs == NULL) {
4595 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4596 goto error;
4597 }
4598
4599 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4600 1, curpx, &rs->rules, file, line, &errmsg);
4601 if (!chk) {
4602 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4603 goto error;
4604 }
4605 chk->index = 0;
4606 LIST_ADDQ(&rs->rules, &chk->list);
4607
4608 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4609 "min-recv", "14",
4610 "on-error", "Not LDAPv3 protocol",
4611 ""},
4612 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4613 if (!chk) {
4614 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4615 goto error;
4616 }
4617 chk->index = 1;
4618 LIST_ADDQ(&rs->rules, &chk->list);
4619
4620 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4621 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4622 if (!chk) {
4623 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4624 goto error;
4625 }
4626 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4627 chk->index = 2;
4628 LIST_ADDQ(&rs->rules, &chk->list);
4629
4630 ruleset_found:
4631 rules->list = &rs->rules;
4632 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4633 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4634
4635 out:
4636 free(errmsg);
4637 return err_code;
4638
4639 error:
4640 free_tcpcheck_ruleset(rs);
4641 err_code |= ERR_ALERT | ERR_FATAL;
4642 goto out;
4643}
4644
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004645int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004646 const char *file, int line)
4647{
4648 struct tcpcheck_ruleset *rs = NULL;
4649 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4650 struct tcpcheck_rule *chk;
4651 char *spop_req = NULL;
4652 char *errmsg = NULL;
4653 int spop_len = 0, err_code = 0;
4654
4655 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4656 err_code |= ERR_WARN;
4657
4658 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4659 goto out;
4660
4661 curpx->options2 &= ~PR_O2_CHK_ANY;
4662 curpx->options2 |= PR_O2_TCPCHK_CHK;
4663
4664 free_tcpcheck_vars(&rules->preset_vars);
4665 rules->list = NULL;
4666 rules->flags = 0;
4667
4668
4669 rs = find_tcpcheck_ruleset("*spop-check");
4670 if (rs)
4671 goto ruleset_found;
4672
4673 rs = create_tcpcheck_ruleset("*spop-check");
4674 if (rs == NULL) {
4675 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4676 goto error;
4677 }
4678
4679 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4680 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4681 goto error;
4682 }
4683 chunk_reset(&trash);
4684 dump_binary(&trash, spop_req, spop_len);
4685 trash.area[trash.data] = '\0';
4686
4687 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4688 1, curpx, &rs->rules, file, line, &errmsg);
4689 if (!chk) {
4690 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4691 goto error;
4692 }
4693 chk->index = 0;
4694 LIST_ADDQ(&rs->rules, &chk->list);
4695
4696 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4697 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4698 if (!chk) {
4699 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4700 goto error;
4701 }
4702 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4703 chk->index = 1;
4704 LIST_ADDQ(&rs->rules, &chk->list);
4705
4706 ruleset_found:
4707 rules->list = &rs->rules;
4708 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4709 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4710
4711 out:
4712 free(spop_req);
4713 free(errmsg);
4714 return err_code;
4715
4716 error:
4717 free_tcpcheck_ruleset(rs);
4718 err_code |= ERR_ALERT | ERR_FATAL;
4719 goto out;
4720}
4721
4722
4723static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4724{
4725 struct tcpcheck_rule *chk = NULL;
4726 struct tcpcheck_http_hdr *hdr = NULL;
4727 char *meth = NULL, *uri = NULL, *vsn = NULL;
4728 char *hdrs, *body;
4729
4730 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4731 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4732 if (hdrs == body)
4733 hdrs = NULL;
4734 if (hdrs) {
4735 *hdrs = '\0';
4736 hdrs +=2;
4737 }
4738 if (body) {
4739 *body = '\0';
4740 body += 4;
4741 }
4742 if (hdrs || body) {
4743 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4744 " Please, consider to use 'http-check send' directive instead.");
4745 }
4746
4747 chk = calloc(1, sizeof(*chk));
4748 if (!chk) {
4749 memprintf(errmsg, "out of memory");
4750 goto error;
4751 }
4752 chk->action = TCPCHK_ACT_SEND;
4753 chk->send.type = TCPCHK_SEND_HTTP;
4754 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4755 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4756 LIST_INIT(&chk->send.http.hdrs);
4757
4758 /* Copy the method, uri and version */
4759 if (*args[cur_arg]) {
4760 if (!*args[cur_arg+1])
4761 uri = args[cur_arg];
4762 else
4763 meth = args[cur_arg];
4764 }
4765 if (*args[cur_arg+1])
4766 uri = args[cur_arg+1];
4767 if (*args[cur_arg+2])
4768 vsn = args[cur_arg+2];
4769
4770 if (meth) {
4771 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4772 chk->send.http.meth.str.area = strdup(meth);
4773 chk->send.http.meth.str.data = strlen(meth);
4774 if (!chk->send.http.meth.str.area) {
4775 memprintf(errmsg, "out of memory");
4776 goto error;
4777 }
4778 }
4779 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004780 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004781 if (!isttest(chk->send.http.uri)) {
4782 memprintf(errmsg, "out of memory");
4783 goto error;
4784 }
4785 }
4786 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004787 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004788 if (!isttest(chk->send.http.vsn)) {
4789 memprintf(errmsg, "out of memory");
4790 goto error;
4791 }
4792 }
4793
4794 /* Copy the header */
4795 if (hdrs) {
4796 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4797 struct h1m h1m;
4798 int i, ret;
4799
4800 /* Build and parse the request */
4801 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4802
4803 h1m.flags = H1_MF_HDRS_ONLY;
4804 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4805 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4806 &h1m, NULL);
4807 if (ret <= 0) {
4808 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4809 goto error;
4810 }
4811
4812 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4813 hdr = calloc(1, sizeof(*hdr));
4814 if (!hdr) {
4815 memprintf(errmsg, "out of memory");
4816 goto error;
4817 }
4818 LIST_INIT(&hdr->value);
4819 hdr->name = istdup(tmp_hdrs[i].n);
4820 if (!hdr->name.ptr) {
4821 memprintf(errmsg, "out of memory");
4822 goto error;
4823 }
4824
4825 ist0(tmp_hdrs[i].v);
4826 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
4827 goto error;
4828 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
4829 }
4830 }
4831
4832 /* Copy the body */
4833 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004834 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004835 if (!isttest(chk->send.http.body)) {
4836 memprintf(errmsg, "out of memory");
4837 goto error;
4838 }
4839 }
4840
4841 return chk;
4842
4843 error:
4844 free_tcpcheck_http_hdr(hdr);
4845 free_tcpcheck(chk, 0);
4846 return NULL;
4847}
4848
4849/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004850int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004851 const char *file, int line)
4852{
4853 struct tcpcheck_ruleset *rs = NULL;
4854 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4855 struct tcpcheck_rule *chk;
4856 char *errmsg = NULL;
4857 int err_code = 0;
4858
4859 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4860 err_code |= ERR_WARN;
4861
4862 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4863 goto out;
4864
4865 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
4866 if (!chk) {
4867 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
4868 goto error;
4869 }
4870 if (errmsg) {
4871 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
4872 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01004873 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004874 }
4875
4876 no_request:
4877 curpx->options2 &= ~PR_O2_CHK_ANY;
4878 curpx->options2 |= PR_O2_TCPCHK_CHK;
4879
4880 free_tcpcheck_vars(&rules->preset_vars);
4881 rules->list = NULL;
4882 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
4883
4884 /* Deduce the ruleset name from the proxy info */
4885 chunk_printf(&trash, "*http-check-%s_%s-%d",
4886 ((curpx == defpx) ? "defaults" : curpx->id),
4887 curpx->conf.file, curpx->conf.line);
4888
4889 rs = find_tcpcheck_ruleset(b_orig(&trash));
4890 if (rs == NULL) {
4891 rs = create_tcpcheck_ruleset(b_orig(&trash));
4892 if (rs == NULL) {
4893 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4894 goto error;
4895 }
4896 }
4897
4898 rules->list = &rs->rules;
4899 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4900 rules->flags |= TCPCHK_RULES_HTTP_CHK;
4901 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
4902 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
4903 rules->list = NULL;
4904 goto error;
4905 }
4906
4907 out:
4908 free(errmsg);
4909 return err_code;
4910
4911 error:
4912 free_tcpcheck_ruleset(rs);
4913 free_tcpcheck(chk, 0);
4914 err_code |= ERR_ALERT | ERR_FATAL;
4915 goto out;
4916}
4917
4918/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004919int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004920 const char *file, int line)
4921{
4922 struct tcpcheck_ruleset *rs = NULL;
4923 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4924 int err_code = 0;
4925
4926 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4927 err_code |= ERR_WARN;
4928
4929 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4930 goto out;
4931
4932 curpx->options2 &= ~PR_O2_CHK_ANY;
4933 curpx->options2 |= PR_O2_TCPCHK_CHK;
4934
4935 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
4936 /* If a tcp-check rulesset is already set, do nothing */
4937 if (rules->list)
4938 goto out;
4939
4940 /* If a tcp-check ruleset is waiting to be used for the current proxy,
4941 * get it.
4942 */
4943 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
4944 goto curpx_ruleset;
4945
4946 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
4947 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
4948 rs = find_tcpcheck_ruleset(b_orig(&trash));
4949 if (rs)
4950 goto ruleset_found;
4951 }
4952
4953 curpx_ruleset:
4954 /* Deduce the ruleset name from the proxy info */
4955 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
4956 ((curpx == defpx) ? "defaults" : curpx->id),
4957 curpx->conf.file, curpx->conf.line);
4958
4959 rs = find_tcpcheck_ruleset(b_orig(&trash));
4960 if (rs == NULL) {
4961 rs = create_tcpcheck_ruleset(b_orig(&trash));
4962 if (rs == NULL) {
4963 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4964 goto error;
4965 }
4966 }
4967
4968 ruleset_found:
4969 free_tcpcheck_vars(&rules->preset_vars);
4970 rules->list = &rs->rules;
4971 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4972 rules->flags |= TCPCHK_RULES_TCP_CHK;
4973
4974 out:
4975 return err_code;
4976
4977 error:
4978 err_code |= ERR_ALERT | ERR_FATAL;
4979 goto out;
4980}
4981
Willy Tarreau51cd5952020-06-05 12:25:38 +02004982static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004983 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02004984 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
4985 { 0, NULL, NULL },
4986}};
4987
4988REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
4989REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
4990REGISTER_POST_DEINIT(deinit_tcpchecks);
4991INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);