blob: 55ecd2383e58c2ca2a86fc69847560d24f846179 [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
1075 conn_prepare(conn, proto, xprt);
1076 cs_attach(cs, check, &check_conn_cb);
1077
Christopher Fauletf7177272020-10-02 13:41:55 +02001078 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1079 conn->send_proxy_ofs = 1;
1080 conn->flags |= CO_FL_SOCKS4;
1081 }
1082 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1083 conn->send_proxy_ofs = 1;
1084 conn->flags |= CO_FL_SOCKS4;
1085 }
1086
1087 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1088 conn->send_proxy_ofs = 1;
1089 conn->flags |= CO_FL_SEND_PROXY;
1090 }
1091 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1092 conn->send_proxy_ofs = 1;
1093 conn->flags |= CO_FL_SEND_PROXY;
1094 }
1095
Willy Tarreau51cd5952020-06-05 12:25:38 +02001096 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001097 if (proto && proto->connect) {
1098 int flags = 0;
1099
1100 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1101 flags |= CONNECT_HAS_DATA;
1102 if (!next || next->action != TCPCHK_ACT_EXPECT)
1103 flags |= CONNECT_DELACK_ALWAYS;
1104 status = proto->connect(conn, flags);
1105 }
1106
1107 if (status != SF_ERR_NONE)
1108 goto fail_check;
1109
Christopher Faulet21ddc742020-07-01 15:26:14 +02001110 conn_set_private(conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001111 conn->ctx = cs;
1112
Willy Tarreau51cd5952020-06-05 12:25:38 +02001113#ifdef USE_OPENSSL
1114 if (connect->sni)
1115 ssl_sock_set_servername(conn, connect->sni);
1116 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1117 ssl_sock_set_servername(conn, s->check.sni);
1118
1119 if (connect->alpn)
1120 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1121 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1122 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1123#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001124
1125 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1126 /* Some servers don't like reset on close */
1127 fdtab[cs->conn->handle.fd].linger_risk = 0;
1128 }
1129
1130 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1131 if (xprt_add_hs(conn) < 0)
1132 status = SF_ERR_RESOURCE;
1133 }
1134
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001135 /* The mux may be initialized now if there isn't server attached to the
1136 * check (email alerts) or if there is a mux proto specified or if there
1137 * is no alpn.
1138 */
1139 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1140 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1141 const struct mux_ops *mux_ops;
1142
1143 if (connect->mux_proto)
1144 mux_ops = connect->mux_proto->mux;
1145 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1146 mux_ops = check->mux_proto->mux;
1147 else {
1148 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1149 ? PROTO_MODE_HTTP
1150 : PROTO_MODE_TCP);
1151
1152 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1153 }
1154 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
1155 status = SF_ERR_INTERNAL;
1156 goto fail_check;
1157 }
1158 }
1159
Willy Tarreau51cd5952020-06-05 12:25:38 +02001160 fail_check:
1161 /* It can return one of :
1162 * - SF_ERR_NONE if everything's OK
1163 * - SF_ERR_SRVTO if there are no more servers
1164 * - SF_ERR_SRVCL if the connection was refused by the server
1165 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1166 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1167 * - SF_ERR_INTERNAL for any other purely internal errors
1168 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1169 * Note that we try to prevent the network stack from sending the ACK during the
1170 * connect() when a pure TCP check is used (without PROXY protocol).
1171 */
1172 switch (status) {
1173 case SF_ERR_NONE:
1174 /* we allow up to min(inter, timeout.connect) for a connection
1175 * to establish but only when timeout.check is set as it may be
1176 * to short for a full check otherwise
1177 */
1178 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1179
1180 if (proxy->timeout.check && proxy->timeout.connect) {
1181 int t_con = tick_add(now_ms, proxy->timeout.connect);
1182 t->expire = tick_first(t->expire, t_con);
1183 }
1184 break;
1185 case SF_ERR_SRVTO: /* ETIMEDOUT */
1186 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1187 case SF_ERR_PRXCOND:
1188 case SF_ERR_RESOURCE:
1189 case SF_ERR_INTERNAL:
1190 chk_report_conn_err(check, errno, 0);
1191 ret = TCPCHK_EVAL_STOP;
1192 goto out;
1193 }
1194
1195 /* don't do anything until the connection is established */
1196 if (conn->flags & CO_FL_WAIT_XPRT) {
1197 if (conn->mux) {
1198 if (next && next->action == TCPCHK_ACT_SEND)
1199 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1200 else
1201 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
1202 }
1203 ret = TCPCHK_EVAL_WAIT;
1204 goto out;
1205 }
1206
1207 out:
1208 if (conn && check->result == CHK_RES_FAILED)
1209 conn->flags |= CO_FL_ERROR;
Christopher Fauletc878f562020-12-09 19:46:38 +01001210
1211 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1212 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1213
Willy Tarreau51cd5952020-06-05 12:25:38 +02001214 return ret;
1215}
1216
1217/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1218 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1219 * TCPCHK_EVAL_STOP if an error occurred.
1220 */
1221enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1222{
1223 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1224 struct tcpcheck_send *send = &rule->send;
1225 struct conn_stream *cs = check->cs;
1226 struct connection *conn = cs_conn(cs);
1227 struct buffer *tmp = NULL;
1228 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001229 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001230
Christopher Fauletb381a502020-11-25 13:47:00 +01001231 if (check->state & CHK_ST_OUT_ALLOC) {
1232 ret = TCPCHK_EVAL_WAIT;
1233 goto out;
1234 }
1235
1236 if (!check_get_buf(check, &check->bo)) {
1237 check->state |= CHK_ST_OUT_ALLOC;
1238 ret = TCPCHK_EVAL_WAIT;
1239 goto out;
1240 }
1241
Christopher Faulet39066c22020-11-25 13:34:51 +01001242 /* Data already pending in the output buffer, send them now */
1243 if (b_data(&check->bo))
1244 goto do_send;
1245
Christopher Fauletb381a502020-11-25 13:47:00 +01001246 /* Always release input buffer when a new send is evaluated */
1247 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001248
1249 switch (send->type) {
1250 case TCPCHK_SEND_STRING:
1251 case TCPCHK_SEND_BINARY:
1252 if (istlen(send->data) >= b_size(&check->bo)) {
1253 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1254 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1255 tcpcheck_get_step_id(check, rule));
1256 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1257 ret = TCPCHK_EVAL_STOP;
1258 goto out;
1259 }
1260 b_putist(&check->bo, send->data);
1261 break;
1262 case TCPCHK_SEND_STRING_LF:
1263 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1264 if (!b_data(&check->bo))
1265 goto out;
1266 break;
1267 case TCPCHK_SEND_BINARY_LF: {
1268 int len = b_size(&check->bo);
1269
1270 tmp = alloc_trash_chunk();
1271 if (!tmp)
1272 goto error_lf;
1273 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1274 if (!b_data(tmp))
1275 goto out;
1276 tmp->area[tmp->data] = '\0';
1277 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1278 goto error_lf;
1279 check->bo.data = len;
1280 break;
1281 }
1282 case TCPCHK_SEND_HTTP: {
1283 struct htx_sl *sl;
1284 struct ist meth, uri, vsn, clen, body;
1285 unsigned int slflags = 0;
1286
1287 tmp = alloc_trash_chunk();
1288 if (!tmp)
1289 goto error_htx;
1290
1291 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1292 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1293 : http_known_methods[send->http.meth.meth]);
1294 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1295 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1296 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1297 }
1298 else
1299 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1300 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1301
1302 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1303 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1304 slflags |= HTX_SL_F_VER_11;
1305 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1306 if (!isttest(send->http.body))
1307 slflags |= HTX_SL_F_BODYLESS;
1308
1309 htx = htx_from_buf(&check->bo);
1310 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1311 if (!sl)
1312 goto error_htx;
1313 sl->info.req.meth = send->http.meth.meth;
1314 if (!http_update_host(htx, sl, uri))
1315 goto error_htx;
1316
1317 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1318 struct tcpcheck_http_hdr *hdr;
1319 struct ist hdr_value;
1320
1321 list_for_each_entry(hdr, &send->http.hdrs, list) {
1322 chunk_reset(tmp);
1323 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1324 if (!b_data(tmp))
1325 continue;
1326 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1327 if (!htx_add_header(htx, hdr->name, hdr_value))
1328 goto error_htx;
1329 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1330 if (!http_update_authority(htx, sl, hdr_value))
1331 goto error_htx;
1332 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001333 if (isteqi(hdr->name, ist("connection")))
1334 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001335 }
1336
1337 }
1338 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1339 chunk_reset(tmp);
1340 httpchk_build_status_header(check->server, tmp);
1341 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1342 goto error_htx;
1343 }
1344
1345
1346 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1347 chunk_reset(tmp);
1348 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1349 body = ist2(b_orig(tmp), b_data(tmp));
1350 }
1351 else
1352 body = send->http.body;
1353 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1354
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001355 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001356 !htx_add_header(htx, ist("Content-length"), clen))
1357 goto error_htx;
1358
1359
1360 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001361 (istlen(body) && !htx_add_data_atonce(htx, body)))
1362 goto error_htx;
1363
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001364 /* no more data are expected */
1365 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001366 htx_to_buf(htx, &check->bo);
1367 break;
1368 }
1369 case TCPCHK_SEND_UNDEF:
1370 /* Should never happen. */
1371 ret = TCPCHK_EVAL_STOP;
1372 goto out;
1373 };
1374
Christopher Faulet39066c22020-11-25 13:34:51 +01001375 do_send:
Willy Tarreau51cd5952020-06-05 12:25:38 +02001376 if (conn->mux->snd_buf(cs, &check->bo,
1377 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1378 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1379 ret = TCPCHK_EVAL_STOP;
1380 goto out;
1381 }
1382 }
1383 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || b_data(&check->bo)) {
1384 cs->conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1385 ret = TCPCHK_EVAL_WAIT;
1386 goto out;
1387 }
1388
1389 out:
1390 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001391 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1392 check_release_buf(check, &check->bo);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001393 return ret;
1394
1395 error_htx:
1396 if (htx) {
1397 htx_reset(htx);
1398 htx_to_buf(htx, &check->bo);
1399 }
1400 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1401 tcpcheck_get_step_id(check, rule));
1402 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1403 ret = TCPCHK_EVAL_STOP;
1404 goto out;
1405
1406 error_lf:
1407 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1408 tcpcheck_get_step_id(check, rule));
1409 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1410 ret = TCPCHK_EVAL_STOP;
1411 goto out;
1412
1413}
1414
1415/* Try to receive data before evaluating a tcp-check expect rule. Returns
1416 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1417 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1418 * TCPCHK_EVAL_STOP if an error occurred.
1419 */
1420enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1421{
1422 struct conn_stream *cs = check->cs;
1423 struct connection *conn = cs_conn(cs);
1424 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1425 size_t max, read, cur_read = 0;
1426 int is_empty;
1427 int read_poll = MAX_READ_POLL_LOOPS;
1428
1429 if (check->wait_list.events & SUB_RETRY_RECV)
1430 goto wait_more_data;
1431
1432 if (cs->flags & CS_FL_EOS)
1433 goto end_recv;
1434
Christopher Fauletb381a502020-11-25 13:47:00 +01001435 if (check->state & CHK_ST_IN_ALLOC)
1436 goto wait_more_data;
1437
1438 if (!check_get_buf(check, &check->bi)) {
1439 check->state |= CHK_ST_IN_ALLOC;
1440 goto wait_more_data;
1441 }
1442
Willy Tarreau51cd5952020-06-05 12:25:38 +02001443 /* errors on the connection and the conn-stream were already checked */
1444
1445 /* prepare to detect if the mux needs more room */
1446 cs->flags &= ~CS_FL_WANT_ROOM;
1447
1448 while ((cs->flags & CS_FL_RCV_MORE) ||
1449 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1450 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1451 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1452 cur_read += read;
1453 if (!read ||
1454 (cs->flags & CS_FL_WANT_ROOM) ||
1455 (--read_poll <= 0) ||
1456 (read < max && read >= global.tune.recv_enough))
1457 break;
1458 }
1459
1460 end_recv:
1461 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1462 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1463 /* Report network errors only if we got no other data. Otherwise
1464 * we'll let the upper layers decide whether the response is OK
1465 * or not. It is very common that an RST sent by the server is
1466 * reported as an error just after the last data chunk.
1467 */
1468 goto stop;
1469 }
1470 if (!cur_read) {
1471 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1472 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
1473 goto wait_more_data;
1474 }
1475 if (is_empty) {
1476 int status;
1477
1478 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1479 tcpcheck_get_step_id(check, rule));
1480 if (rule->comment)
1481 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1482
1483 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1484 set_server_check_status(check, status, trash.area);
1485 goto stop;
1486 }
1487 }
1488
1489 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001490 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1491 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001492 return ret;
1493
1494 stop:
1495 ret = TCPCHK_EVAL_STOP;
1496 goto out;
1497
1498 wait_more_data:
1499 ret = TCPCHK_EVAL_WAIT;
1500 goto out;
1501}
1502
1503/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1504 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1505 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1506 * error occurred.
1507 */
1508enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1509{
1510 struct htx *htx = htxbuf(&check->bi);
1511 struct htx_sl *sl;
1512 struct htx_blk *blk;
1513 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1514 struct tcpcheck_expect *expect = &rule->expect;
1515 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1516 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1517 struct ist desc = IST_NULL;
1518 int i, match, inverse;
1519
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001520 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001521
1522 if (htx->flags & HTX_FL_PARSING_ERROR) {
1523 status = HCHK_STATUS_L7RSP;
1524 goto error;
1525 }
1526
1527 if (htx_is_empty(htx)) {
1528 if (last_read) {
1529 status = HCHK_STATUS_L7RSP;
1530 goto error;
1531 }
1532 goto wait_more_data;
1533 }
1534
1535 sl = http_get_stline(htx);
1536 check->code = sl->info.res.status;
1537
1538 if (check->server &&
1539 (check->server->proxy->options & PR_O_DISABLE404) &&
1540 (check->server->next_state != SRV_ST_STOPPED) &&
1541 (check->code == 404)) {
1542 /* 404 may be accepted as "stopping" only if the server was up */
1543 goto out;
1544 }
1545
1546 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1547 /* Make GCC happy ; initialize match to a failure state. */
1548 match = inverse;
1549 status = expect->err_status;
1550
1551 switch (expect->type) {
1552 case TCPCHK_EXPECT_HTTP_STATUS:
1553 match = 0;
1554 for (i = 0; i < expect->codes.num; i++) {
1555 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1556 sl->info.res.status <= expect->codes.codes[i][1]) {
1557 match = 1;
1558 break;
1559 }
1560 }
1561
1562 /* Set status and description in case of error */
1563 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1564 if (LIST_ISEMPTY(&expect->onerror_fmt))
1565 desc = htx_sl_res_reason(sl);
1566 break;
1567 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1568 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1569
1570 /* Set status and description in case of error */
1571 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1572 if (LIST_ISEMPTY(&expect->onerror_fmt))
1573 desc = htx_sl_res_reason(sl);
1574 break;
1575
1576 case TCPCHK_EXPECT_HTTP_HEADER: {
1577 struct http_hdr_ctx ctx;
1578 struct ist npat, vpat, value;
1579 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1580
1581 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1582 nbuf = alloc_trash_chunk();
1583 if (!nbuf) {
1584 status = HCHK_STATUS_L7RSP;
1585 desc = ist("Failed to allocate buffer to eval log-format string");
1586 goto error;
1587 }
1588 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1589 if (!b_data(nbuf)) {
1590 status = HCHK_STATUS_L7RSP;
1591 desc = ist("log-format string evaluated to an empty string");
1592 goto error;
1593 }
1594 npat = ist2(b_orig(nbuf), b_data(nbuf));
1595 }
1596 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1597 npat = expect->hdr.name;
1598
1599 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1600 vbuf = alloc_trash_chunk();
1601 if (!vbuf) {
1602 status = HCHK_STATUS_L7RSP;
1603 desc = ist("Failed to allocate buffer to eval log-format string");
1604 goto error;
1605 }
1606 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1607 if (!b_data(vbuf)) {
1608 status = HCHK_STATUS_L7RSP;
1609 desc = ist("log-format string evaluated to an empty string");
1610 goto error;
1611 }
1612 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1613 }
1614 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1615 vpat = expect->hdr.value;
1616
1617 match = 0;
1618 ctx.blk = NULL;
1619 while (1) {
1620 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1621 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1622 if (!http_find_str_header(htx, npat, &ctx, full))
1623 goto end_of_match;
1624 break;
1625 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1626 if (!http_find_pfx_header(htx, npat, &ctx, full))
1627 goto end_of_match;
1628 break;
1629 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1630 if (!http_find_sfx_header(htx, npat, &ctx, full))
1631 goto end_of_match;
1632 break;
1633 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1634 if (!http_find_sub_header(htx, npat, &ctx, full))
1635 goto end_of_match;
1636 break;
1637 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1638 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1639 goto end_of_match;
1640 break;
1641 default:
1642 /* should never happen */
1643 goto end_of_match;
1644 }
1645
1646 /* A header has matched the name pattern, let's test its
1647 * value now (always defined from there). If there is no
1648 * value pattern, it is a good match.
1649 */
1650
1651 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1652 match = 1;
1653 goto end_of_match;
1654 }
1655
1656 value = ctx.value;
1657 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1658 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1659 if (isteq(value, vpat)) {
1660 match = 1;
1661 goto end_of_match;
1662 }
1663 break;
1664 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1665 if (istlen(value) < istlen(vpat))
1666 break;
1667 value = ist2(istptr(value), istlen(vpat));
1668 if (isteq(value, vpat)) {
1669 match = 1;
1670 goto end_of_match;
1671 }
1672 break;
1673 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1674 if (istlen(value) < istlen(vpat))
1675 break;
1676 value = ist2(istptr(value) + istlen(value) - istlen(vpat), 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_SUB:
1683 if (isttest(istist(value, vpat))) {
1684 match = 1;
1685 goto end_of_match;
1686 }
1687 break;
1688 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1689 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1690 match = 1;
1691 goto end_of_match;
1692 }
1693 break;
1694 }
1695 }
1696
1697 end_of_match:
1698 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1699 if (LIST_ISEMPTY(&expect->onerror_fmt))
1700 desc = htx_sl_res_reason(sl);
1701 break;
1702 }
1703
1704 case TCPCHK_EXPECT_HTTP_BODY:
1705 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1706 case TCPCHK_EXPECT_HTTP_BODY_LF:
1707 match = 0;
1708 chunk_reset(&trash);
1709 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1710 enum htx_blk_type type = htx_get_blk_type(blk);
1711
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001712 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001713 break;
1714 if (type == HTX_BLK_DATA) {
1715 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1716 break;
1717 }
1718 }
1719
1720 if (!b_data(&trash)) {
1721 if (!last_read)
1722 goto wait_more_data;
1723 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1724 if (LIST_ISEMPTY(&expect->onerror_fmt))
1725 desc = ist("HTTP content check could not find a response body");
1726 goto error;
1727 }
1728
1729 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1730 tmp = alloc_trash_chunk();
1731 if (!tmp) {
1732 status = HCHK_STATUS_L7RSP;
1733 desc = ist("Failed to allocate buffer to eval log-format string");
1734 goto error;
1735 }
1736 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1737 if (!b_data(tmp)) {
1738 status = HCHK_STATUS_L7RSP;
1739 desc = ist("log-format string evaluated to an empty string");
1740 goto error;
1741 }
1742 }
1743
1744 if (!last_read &&
1745 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1746 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1747 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1748 ret = TCPCHK_EVAL_WAIT;
1749 goto out;
1750 }
1751
1752 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1753 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1754 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1755 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1756 else
1757 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1758
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001759 /* Wait for more data on mismatch only if no minimum is defined (-1),
1760 * otherwise the absence of match is already conclusive.
1761 */
1762 if (!match && !last_read && (expect->min_recv == -1)) {
1763 ret = TCPCHK_EVAL_WAIT;
1764 goto out;
1765 }
1766
Willy Tarreau51cd5952020-06-05 12:25:38 +02001767 /* Set status and description in case of error */
1768 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1769 if (LIST_ISEMPTY(&expect->onerror_fmt))
1770 desc = (inverse
1771 ? ist("HTTP check matched unwanted content")
1772 : ist("HTTP content check did not match"));
1773 break;
1774
1775
1776 default:
1777 /* should never happen */
1778 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1779 goto error;
1780 }
1781
Willy Tarreau51cd5952020-06-05 12:25:38 +02001782 if (!(match ^ inverse))
1783 goto error;
1784
1785 out:
1786 free_trash_chunk(tmp);
1787 free_trash_chunk(nbuf);
1788 free_trash_chunk(vbuf);
1789 free_trash_chunk(msg);
1790 return ret;
1791
1792 error:
1793 ret = TCPCHK_EVAL_STOP;
1794 msg = alloc_trash_chunk();
1795 if (msg)
1796 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1797 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1798 goto out;
1799
1800 wait_more_data:
1801 ret = TCPCHK_EVAL_WAIT;
1802 goto out;
1803}
1804
1805/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1806 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1807 * if an error occurred.
1808 */
1809enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1810{
1811 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1812 struct tcpcheck_expect *expect = &rule->expect;
1813 struct buffer *msg = NULL, *tmp = NULL;
1814 struct ist desc = IST_NULL;
1815 enum healthcheck_status status;
1816 int match, inverse;
1817
1818 last_read |= b_full(&check->bi);
1819
1820 /* The current expect might need more data than the previous one, check again
1821 * that the minimum amount data required to match is respected.
1822 */
1823 if (!last_read) {
1824 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1825 (b_data(&check->bi) < istlen(expect->data))) {
1826 ret = TCPCHK_EVAL_WAIT;
1827 goto out;
1828 }
1829 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1830 ret = TCPCHK_EVAL_WAIT;
1831 goto out;
1832 }
1833 }
1834
1835 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1836 /* Make GCC happy ; initialize match to a failure state. */
1837 match = inverse;
1838 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1839
1840 switch (expect->type) {
1841 case TCPCHK_EXPECT_STRING:
1842 case TCPCHK_EXPECT_BINARY:
1843 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
1844 break;
1845 case TCPCHK_EXPECT_STRING_REGEX:
1846 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
1847 break;
1848
1849 case TCPCHK_EXPECT_BINARY_REGEX:
1850 chunk_reset(&trash);
1851 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
1852 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
1853 break;
1854
1855 case TCPCHK_EXPECT_STRING_LF:
1856 case TCPCHK_EXPECT_BINARY_LF:
1857 match = 0;
1858 tmp = alloc_trash_chunk();
1859 if (!tmp) {
1860 status = HCHK_STATUS_L7RSP;
1861 desc = ist("Failed to allocate buffer to eval format string");
1862 goto error;
1863 }
1864 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1865 if (!b_data(tmp)) {
1866 status = HCHK_STATUS_L7RSP;
1867 desc = ist("log-format string evaluated to an empty string");
1868 goto error;
1869 }
1870 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
1871 int len = tmp->data;
1872 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
1873 status = HCHK_STATUS_L7RSP;
1874 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
1875 goto error;
1876 }
1877 tmp->data = len;
1878 }
1879 if (b_data(&check->bi) < tmp->data) {
1880 if (!last_read) {
1881 ret = TCPCHK_EVAL_WAIT;
1882 goto out;
1883 }
1884 break;
1885 }
1886 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
1887 break;
1888
1889 case TCPCHK_EXPECT_CUSTOM:
1890 if (expect->custom)
1891 ret = expect->custom(check, rule, last_read);
1892 goto out;
1893 default:
1894 /* Should never happen. */
1895 ret = TCPCHK_EVAL_STOP;
1896 goto out;
1897 }
1898
1899
1900 /* Wait for more data on mismatch only if no minimum is defined (-1),
1901 * otherwise the absence of match is already conclusive.
1902 */
1903 if (!match && !last_read && (expect->min_recv == -1)) {
1904 ret = TCPCHK_EVAL_WAIT;
1905 goto out;
1906 }
1907
1908 /* Result as expected, next rule. */
1909 if (match ^ inverse)
1910 goto out;
1911
1912 error:
1913 /* From this point on, we matched something we did not want, this is an error state. */
1914 ret = TCPCHK_EVAL_STOP;
1915 msg = alloc_trash_chunk();
1916 if (msg)
1917 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
1918 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1919 free_trash_chunk(msg);
1920
1921 out:
1922 free_trash_chunk(tmp);
1923 return ret;
1924}
1925
1926/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
1927 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
1928 * waits.
1929 */
1930enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
1931{
1932 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1933 struct act_rule *act_rule;
1934 enum act_return act_ret;
1935
1936 act_rule =rule->action_kw.rule;
1937 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
1938 if (act_ret != ACT_RET_CONT) {
1939 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
1940 tcpcheck_get_step_id(check, rule));
1941 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1942 ret = TCPCHK_EVAL_STOP;
1943 }
1944
1945 return ret;
1946}
1947
1948/* Executes a tcp-check ruleset. Note that this is called both from the
1949 * connection's wake() callback and from the check scheduling task. It returns
1950 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
1951 * presenting the risk of an fd replacement.
1952 *
1953 * Please do NOT place any return statement in this function and only leave
1954 * via the out_end_tcpcheck label after setting retcode.
1955 */
1956int tcpcheck_main(struct check *check)
1957{
1958 struct tcpcheck_rule *rule;
1959 struct conn_stream *cs = check->cs;
1960 struct connection *conn = cs_conn(cs);
1961 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01001962 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001963 enum tcpcheck_eval_ret eval_ret;
1964
1965 /* here, we know that the check is complete or that it failed */
1966 if (check->result != CHK_RES_UNKNOWN)
1967 goto out;
1968
1969 /* Note: the conn-stream and the connection may only be undefined before
1970 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001971 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02001972 */
1973
1974 /* 1- check for connection error, if any */
1975 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
1976 goto out_end_tcpcheck;
1977
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001978 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02001979 * is defined. */
1980 else if (check->current_step)
1981 rule = check->current_step;
1982
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001983 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02001984 * tcp-check variables */
1985 else {
1986 struct tcpcheck_var *var;
1987
1988 /* First evaluation, create a session */
1989 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
1990 if (!check->sess) {
1991 chunk_printf(&trash, "TCPCHK error allocating check session");
1992 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1993 goto out_end_tcpcheck;
1994 }
1995 vars_init(&check->vars, SCOPE_CHECK);
1996 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
1997
1998 /* Preset tcp-check variables */
1999 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2000 struct sample smp;
2001
2002 memset(&smp, 0, sizeof(smp));
2003 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2004 smp.data = var->data;
2005 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2006 }
2007 }
2008
2009 /* Now evaluate the tcp-check rules */
2010
2011 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2012 check->code = 0;
2013 switch (rule->action) {
2014 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002015 /* Not the first connection, release it first */
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002016 if (cs && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002017 check->state |= CHK_ST_CLOSE_CONN;
2018 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002019 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002020
2021 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002022
2023 /* We are still waiting the connection gets closed */
2024 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
2025 eval_ret = TCPCHK_EVAL_WAIT;
2026 break;
2027 }
2028
Willy Tarreau51cd5952020-06-05 12:25:38 +02002029 eval_ret = tcpcheck_eval_connect(check, rule);
2030
2031 /* Refresh conn-stream and connection */
2032 cs = check->cs;
2033 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002034 last_read = 0;
2035 must_read = ((cs && IS_HTX_CS(cs)) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002036 break;
2037 case TCPCHK_ACT_SEND:
2038 check->current_step = rule;
2039 eval_ret = tcpcheck_eval_send(check, rule);
2040 must_read = 1;
2041 break;
2042 case TCPCHK_ACT_EXPECT:
2043 check->current_step = rule;
2044 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002045 eval_ret = tcpcheck_eval_recv(check, rule);
2046 if (eval_ret == TCPCHK_EVAL_STOP)
2047 goto out_end_tcpcheck;
2048 else if (eval_ret == TCPCHK_EVAL_WAIT)
2049 goto out;
2050 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2051 must_read = 0;
2052 }
2053
2054 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2055 ? tcpcheck_eval_expect_http(check, rule, last_read)
2056 : tcpcheck_eval_expect(check, rule, last_read));
2057
2058 if (eval_ret == TCPCHK_EVAL_WAIT) {
2059 check->current_step = rule->expect.head;
2060 if (!(check->wait_list.events & SUB_RETRY_RECV))
2061 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2062 }
2063 break;
2064 case TCPCHK_ACT_ACTION_KW:
2065 /* Don't update the current step */
2066 eval_ret = tcpcheck_eval_action_kw(check, rule);
2067 break;
2068 default:
2069 /* Otherwise, just go to the next one and don't update
2070 * the current step
2071 */
2072 eval_ret = TCPCHK_EVAL_CONTINUE;
2073 break;
2074 }
2075
2076 switch (eval_ret) {
2077 case TCPCHK_EVAL_CONTINUE:
2078 break;
2079 case TCPCHK_EVAL_WAIT:
2080 goto out;
2081 case TCPCHK_EVAL_STOP:
2082 goto out_end_tcpcheck;
2083 }
2084 }
2085
2086 /* All rules was evaluated */
2087 if (check->current_step) {
2088 rule = check->current_step;
2089
2090 if (rule->action == TCPCHK_ACT_EXPECT) {
2091 struct buffer *msg;
2092 enum healthcheck_status status;
2093
2094 if (check->server &&
2095 (check->server->proxy->options & PR_O_DISABLE404) &&
2096 (check->server->next_state != SRV_ST_STOPPED) &&
2097 (check->code == 404)) {
2098 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
2099 goto out_end_tcpcheck;
2100 }
2101
2102 msg = alloc_trash_chunk();
2103 if (msg)
2104 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2105 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2106 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2107 free_trash_chunk(msg);
2108 }
2109 else if (rule->action == TCPCHK_ACT_CONNECT) {
2110 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2111 enum healthcheck_status status = HCHK_STATUS_L4OK;
2112#ifdef USE_OPENSSL
2113 if (ssl_sock_is_ssl(conn))
2114 status = HCHK_STATUS_L6OK;
2115#endif
2116 set_server_check_status(check, status, msg);
2117 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002118 else
2119 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002120 }
2121 else
2122 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
2123
2124 out_end_tcpcheck:
2125 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2126 chk_report_conn_err(check, errno, 0);
2127
Christopher Fauletb381a502020-11-25 13:47:00 +01002128 /* the tcpcheck is finished, release in/out buffer now */
2129 check_release_buf(check, &check->bi);
2130 check_release_buf(check, &check->bo);
2131
Willy Tarreau51cd5952020-06-05 12:25:38 +02002132 out:
2133 return retcode;
2134}
2135
2136
2137/**************************************************************************/
2138/******************* Internals to parse tcp-check rules *******************/
2139/**************************************************************************/
2140struct action_kw_list tcp_check_keywords = {
2141 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2142};
2143
2144/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2145 * returned on error.
2146 */
2147struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2148 struct list *rules, struct action_kw *kw,
2149 const char *file, int line, char **errmsg)
2150{
2151 struct tcpcheck_rule *chk = NULL;
2152 struct act_rule *actrule = NULL;
2153
2154 actrule = calloc(1, sizeof(*actrule));
2155 if (!actrule) {
2156 memprintf(errmsg, "out of memory");
2157 goto error;
2158 }
2159 actrule->kw = kw;
2160 actrule->from = ACT_F_TCP_CHK;
2161
2162 cur_arg++;
2163 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2164 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2165 goto error;
2166 }
2167
2168 chk = calloc(1, sizeof(*chk));
2169 if (!chk) {
2170 memprintf(errmsg, "out of memory");
2171 goto error;
2172 }
2173 chk->action = TCPCHK_ACT_ACTION_KW;
2174 chk->action_kw.rule = actrule;
2175 return chk;
2176
2177 error:
2178 free(actrule);
2179 return NULL;
2180}
2181
2182/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2183 * returned on error.
2184 */
2185struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2186 const char *file, int line, char **errmsg)
2187{
2188 struct tcpcheck_rule *chk = NULL;
2189 struct sockaddr_storage *sk = NULL;
2190 char *comment = NULL, *sni = NULL, *alpn = NULL;
2191 struct sample_expr *port_expr = NULL;
2192 const struct mux_proto_list *mux_proto = NULL;
2193 unsigned short conn_opts = 0;
2194 long port = 0;
2195 int alpn_len = 0;
2196
2197 list_for_each_entry(chk, rules, list) {
2198 if (chk->action == TCPCHK_ACT_CONNECT)
2199 break;
2200 if (chk->action == TCPCHK_ACT_COMMENT ||
2201 chk->action == TCPCHK_ACT_ACTION_KW ||
2202 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2203 continue;
2204
2205 memprintf(errmsg, "first step MUST also be a 'connect', "
2206 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2207 "when there is a 'connect' step in the tcp-check ruleset");
2208 goto error;
2209 }
2210
2211 cur_arg++;
2212 while (*(args[cur_arg])) {
2213 if (strcmp(args[cur_arg], "default") == 0)
2214 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2215 else if (strcmp(args[cur_arg], "addr") == 0) {
2216 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002217
2218 if (!*(args[cur_arg+1])) {
2219 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2220 goto error;
2221 }
2222
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002223 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2224 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002225 if (!sk) {
2226 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2227 goto error;
2228 }
2229
Willy Tarreau51cd5952020-06-05 12:25:38 +02002230 cur_arg++;
2231 }
2232 else if (strcmp(args[cur_arg], "port") == 0) {
2233 const char *p, *end;
2234
2235 if (!*(args[cur_arg+1])) {
2236 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2237 goto error;
2238 }
2239 cur_arg++;
2240
2241 port = 0;
2242 release_sample_expr(port_expr);
2243 p = args[cur_arg]; end = p + strlen(p);
2244 port = read_uint(&p, end);
2245 if (p != end) {
2246 int idx = 0;
2247
2248 px->conf.args.ctx = ARGC_SRV;
2249 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
2250 file, line, errmsg, &px->conf.args, NULL);
2251
2252 if (!port_expr) {
2253 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2254 goto error;
2255 }
2256 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2257 memprintf(errmsg, "error detected while parsing port expression : "
2258 " fetch method '%s' extracts information from '%s', "
2259 "none of which is available here.\n",
2260 args[cur_arg], sample_src_names(port_expr->fetch->use));
2261 goto error;
2262 }
2263 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2264 }
2265 else if (port > 65535 || port < 1) {
2266 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2267 args[cur_arg]);
2268 goto error;
2269 }
2270 }
2271 else if (strcmp(args[cur_arg], "proto") == 0) {
2272 if (!*(args[cur_arg+1])) {
2273 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2274 goto error;
2275 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002276 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002277 if (!mux_proto) {
2278 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2279 goto error;
2280 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002281
2282 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2283 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2284 goto error;
2285 }
2286 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2287 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2288 goto error;
2289 }
2290
Willy Tarreau51cd5952020-06-05 12:25:38 +02002291 cur_arg++;
2292 }
2293 else if (strcmp(args[cur_arg], "comment") == 0) {
2294 if (!*(args[cur_arg+1])) {
2295 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2296 goto error;
2297 }
2298 cur_arg++;
2299 free(comment);
2300 comment = strdup(args[cur_arg]);
2301 if (!comment) {
2302 memprintf(errmsg, "out of memory");
2303 goto error;
2304 }
2305 }
2306 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2307 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2308 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2309 conn_opts |= TCPCHK_OPT_SOCKS4;
2310 else if (strcmp(args[cur_arg], "linger") == 0)
2311 conn_opts |= TCPCHK_OPT_LINGER;
2312#ifdef USE_OPENSSL
2313 else if (strcmp(args[cur_arg], "ssl") == 0) {
2314 px->options |= PR_O_TCPCHK_SSL;
2315 conn_opts |= TCPCHK_OPT_SSL;
2316 }
2317 else if (strcmp(args[cur_arg], "sni") == 0) {
2318 if (!*(args[cur_arg+1])) {
2319 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2320 goto error;
2321 }
2322 cur_arg++;
2323 free(sni);
2324 sni = strdup(args[cur_arg]);
2325 if (!sni) {
2326 memprintf(errmsg, "out of memory");
2327 goto error;
2328 }
2329 }
2330 else if (strcmp(args[cur_arg], "alpn") == 0) {
2331#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2332 free(alpn);
2333 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2334 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2335 goto error;
2336 }
2337 cur_arg++;
2338#else
2339 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2340 goto error;
2341#endif
2342 }
2343#endif /* USE_OPENSSL */
2344
2345 else {
2346 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2347#ifdef USE_OPENSSL
2348 ", 'ssl', 'sni', 'alpn'"
2349#endif /* USE_OPENSSL */
2350 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2351 args[cur_arg]);
2352 goto error;
2353 }
2354 cur_arg++;
2355 }
2356
2357 chk = calloc(1, sizeof(*chk));
2358 if (!chk) {
2359 memprintf(errmsg, "out of memory");
2360 goto error;
2361 }
2362 chk->action = TCPCHK_ACT_CONNECT;
2363 chk->comment = comment;
2364 chk->connect.port = port;
2365 chk->connect.options = conn_opts;
2366 chk->connect.sni = sni;
2367 chk->connect.alpn = alpn;
2368 chk->connect.alpn_len= alpn_len;
2369 chk->connect.port_expr= port_expr;
2370 chk->connect.mux_proto= mux_proto;
2371 if (sk)
2372 chk->connect.addr = *sk;
2373 return chk;
2374
2375 error:
2376 free(alpn);
2377 free(sni);
2378 free(comment);
2379 release_sample_expr(port_expr);
2380 return NULL;
2381}
2382
2383/* Parses and creates a tcp-check send rule. NULL is returned on error */
2384struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2385 const char *file, int line, char **errmsg)
2386{
2387 struct tcpcheck_rule *chk = NULL;
2388 char *comment = NULL, *data = NULL;
2389 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2390
2391 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2392 type = TCPCHK_SEND_BINARY_LF;
2393 else if (strcmp(args[cur_arg], "send-binary") == 0)
2394 type = TCPCHK_SEND_BINARY;
2395 else if (strcmp(args[cur_arg], "send-lf") == 0)
2396 type = TCPCHK_SEND_STRING_LF;
2397 else if (strcmp(args[cur_arg], "send") == 0)
2398 type = TCPCHK_SEND_STRING;
2399
2400 if (!*(args[cur_arg+1])) {
2401 memprintf(errmsg, "'%s' expects a %s as argument",
2402 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2403 goto error;
2404 }
2405
2406 data = args[cur_arg+1];
2407
2408 cur_arg += 2;
2409 while (*(args[cur_arg])) {
2410 if (strcmp(args[cur_arg], "comment") == 0) {
2411 if (!*(args[cur_arg+1])) {
2412 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2413 goto error;
2414 }
2415 cur_arg++;
2416 free(comment);
2417 comment = strdup(args[cur_arg]);
2418 if (!comment) {
2419 memprintf(errmsg, "out of memory");
2420 goto error;
2421 }
2422 }
2423 else {
2424 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2425 args[cur_arg]);
2426 goto error;
2427 }
2428 cur_arg++;
2429 }
2430
2431 chk = calloc(1, sizeof(*chk));
2432 if (!chk) {
2433 memprintf(errmsg, "out of memory");
2434 goto error;
2435 }
2436 chk->action = TCPCHK_ACT_SEND;
2437 chk->comment = comment;
2438 chk->send.type = type;
2439
2440 switch (chk->send.type) {
2441 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002442 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002443 if (!isttest(chk->send.data)) {
2444 memprintf(errmsg, "out of memory");
2445 goto error;
2446 }
2447 break;
2448 case TCPCHK_SEND_BINARY: {
2449 int len = chk->send.data.len;
2450 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2451 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2452 goto error;
2453 }
2454 chk->send.data.len = len;
2455 break;
2456 }
2457 case TCPCHK_SEND_STRING_LF:
2458 case TCPCHK_SEND_BINARY_LF:
2459 LIST_INIT(&chk->send.fmt);
2460 px->conf.args.ctx = ARGC_SRV;
2461 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2462 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2463 goto error;
2464 }
2465 break;
2466 case TCPCHK_SEND_HTTP:
2467 case TCPCHK_SEND_UNDEF:
2468 goto error;
2469 }
2470
2471 return chk;
2472
2473 error:
2474 free(chk);
2475 free(comment);
2476 return NULL;
2477}
2478
2479/* Parses and creates a http-check send rule. NULL is returned on error */
2480struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2481 const char *file, int line, char **errmsg)
2482{
2483 struct tcpcheck_rule *chk = NULL;
2484 struct tcpcheck_http_hdr *hdr = NULL;
2485 struct http_hdr hdrs[global.tune.max_http_hdr];
2486 char *meth = NULL, *uri = NULL, *vsn = NULL;
2487 char *body = NULL, *comment = NULL;
2488 unsigned int flags = 0;
2489 int i = 0, host_hdr = -1;
2490
2491 cur_arg++;
2492 while (*(args[cur_arg])) {
2493 if (strcmp(args[cur_arg], "meth") == 0) {
2494 if (!*(args[cur_arg+1])) {
2495 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2496 goto error;
2497 }
2498 cur_arg++;
2499 meth = args[cur_arg];
2500 }
2501 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2502 if (!*(args[cur_arg+1])) {
2503 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2504 goto error;
2505 }
2506 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2507 if (strcmp(args[cur_arg], "uri-lf") == 0)
2508 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2509 cur_arg++;
2510 uri = args[cur_arg];
2511 }
2512 else if (strcmp(args[cur_arg], "ver") == 0) {
2513 if (!*(args[cur_arg+1])) {
2514 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2515 goto error;
2516 }
2517 cur_arg++;
2518 vsn = args[cur_arg];
2519 }
2520 else if (strcmp(args[cur_arg], "hdr") == 0) {
2521 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2522 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2523 goto error;
2524 }
2525
2526 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2527 if (host_hdr >= 0) {
2528 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2529 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2530 goto error;
2531 }
2532 host_hdr = i;
2533 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002534 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002535 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2536 goto skip_hdr;
2537
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002538 hdrs[i].n = ist(args[cur_arg + 1]);
2539 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002540 i++;
2541 skip_hdr:
2542 cur_arg += 2;
2543 }
2544 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2545 if (!*(args[cur_arg+1])) {
2546 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2547 goto error;
2548 }
2549 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2550 if (strcmp(args[cur_arg], "body-lf") == 0)
2551 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2552 cur_arg++;
2553 body = args[cur_arg];
2554 }
2555 else if (strcmp(args[cur_arg], "comment") == 0) {
2556 if (!*(args[cur_arg+1])) {
2557 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2558 goto error;
2559 }
2560 cur_arg++;
2561 free(comment);
2562 comment = strdup(args[cur_arg]);
2563 if (!comment) {
2564 memprintf(errmsg, "out of memory");
2565 goto error;
2566 }
2567 }
2568 else {
2569 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2570 " but got '%s' as argument.", args[cur_arg]);
2571 goto error;
2572 }
2573 cur_arg++;
2574 }
2575
2576 hdrs[i].n = hdrs[i].v = IST_NULL;
2577
2578 chk = calloc(1, sizeof(*chk));
2579 if (!chk) {
2580 memprintf(errmsg, "out of memory");
2581 goto error;
2582 }
2583 chk->action = TCPCHK_ACT_SEND;
2584 chk->comment = comment; comment = NULL;
2585 chk->send.type = TCPCHK_SEND_HTTP;
2586 chk->send.http.flags = flags;
2587 LIST_INIT(&chk->send.http.hdrs);
2588
2589 if (meth) {
2590 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2591 chk->send.http.meth.str.area = strdup(meth);
2592 chk->send.http.meth.str.data = strlen(meth);
2593 if (!chk->send.http.meth.str.area) {
2594 memprintf(errmsg, "out of memory");
2595 goto error;
2596 }
2597 }
2598 if (uri) {
2599 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2600 LIST_INIT(&chk->send.http.uri_fmt);
2601 px->conf.args.ctx = ARGC_SRV;
2602 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2603 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2604 goto error;
2605 }
2606 }
2607 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002608 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002609 if (!isttest(chk->send.http.uri)) {
2610 memprintf(errmsg, "out of memory");
2611 goto error;
2612 }
2613 }
2614 }
2615 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002616 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002617 if (!isttest(chk->send.http.vsn)) {
2618 memprintf(errmsg, "out of memory");
2619 goto error;
2620 }
2621 }
2622 for (i = 0; istlen(hdrs[i].n); i++) {
2623 hdr = calloc(1, sizeof(*hdr));
2624 if (!hdr) {
2625 memprintf(errmsg, "out of memory");
2626 goto error;
2627 }
2628 LIST_INIT(&hdr->value);
2629 hdr->name = istdup(hdrs[i].n);
2630 if (!isttest(hdr->name)) {
2631 memprintf(errmsg, "out of memory");
2632 goto error;
2633 }
2634
2635 ist0(hdrs[i].v);
2636 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2637 goto error;
2638 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
2639 hdr = NULL;
2640 }
2641
2642 if (body) {
2643 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2644 LIST_INIT(&chk->send.http.body_fmt);
2645 px->conf.args.ctx = ARGC_SRV;
2646 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2647 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2648 goto error;
2649 }
2650 }
2651 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002652 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002653 if (!isttest(chk->send.http.body)) {
2654 memprintf(errmsg, "out of memory");
2655 goto error;
2656 }
2657 }
2658 }
2659
2660 return chk;
2661
2662 error:
2663 free_tcpcheck_http_hdr(hdr);
2664 free_tcpcheck(chk, 0);
2665 free(comment);
2666 return NULL;
2667}
2668
2669/* Parses and creates a http-check comment rule. NULL is returned on error */
2670struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2671 const char *file, int line, char **errmsg)
2672{
2673 struct tcpcheck_rule *chk = NULL;
2674 char *comment = NULL;
2675
2676 if (!*(args[cur_arg+1])) {
2677 memprintf(errmsg, "expects a string as argument");
2678 goto error;
2679 }
2680 cur_arg++;
2681 comment = strdup(args[cur_arg]);
2682 if (!comment) {
2683 memprintf(errmsg, "out of memory");
2684 goto error;
2685 }
2686
2687 chk = calloc(1, sizeof(*chk));
2688 if (!chk) {
2689 memprintf(errmsg, "out of memory");
2690 goto error;
2691 }
2692 chk->action = TCPCHK_ACT_COMMENT;
2693 chk->comment = comment;
2694 return chk;
2695
2696 error:
2697 free(comment);
2698 return NULL;
2699}
2700
2701/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2702 * on error. <proto> is set to the right protocol flags (covered by the
2703 * TCPCHK_RULES_PROTO_CHK mask).
2704 */
2705struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2706 struct list *rules, unsigned int proto,
2707 const char *file, int line, char **errmsg)
2708{
2709 struct tcpcheck_rule *prev_check, *chk = NULL;
2710 struct sample_expr *status_expr = NULL;
2711 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2712 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2713 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2714 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2715 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2716 unsigned int flags = 0;
2717 long min_recv = -1;
2718 int inverse = 0;
2719
2720 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2721 if (!*(args[cur_arg+1])) {
2722 memprintf(errmsg, "expects at least a matching pattern as arguments");
2723 goto error;
2724 }
2725
2726 cur_arg++;
2727 while (*(args[cur_arg])) {
2728 int in_pattern = 0;
2729
2730 rescan:
2731 if (strcmp(args[cur_arg], "min-recv") == 0) {
2732 if (in_pattern) {
2733 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2734 goto error;
2735 }
2736 if (!*(args[cur_arg+1])) {
2737 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2738 goto error;
2739 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002740 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002741 cur_arg++;
2742 min_recv = atol(args[cur_arg]);
2743 if (min_recv < -1 || min_recv > INT_MAX) {
2744 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2745 goto error;
2746 }
2747 }
2748 else if (*(args[cur_arg]) == '!') {
2749 in_pattern = 1;
2750 while (*(args[cur_arg]) == '!') {
2751 inverse = !inverse;
2752 args[cur_arg]++;
2753 }
2754 if (!*(args[cur_arg]))
2755 cur_arg++;
2756 goto rescan;
2757 }
2758 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2759 if (type != TCPCHK_EXPECT_UNDEF) {
2760 memprintf(errmsg, "only on pattern expected");
2761 goto error;
2762 }
2763 if (proto != TCPCHK_RULES_HTTP_CHK)
2764 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2765 else
2766 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2767
2768 if (!*(args[cur_arg+1])) {
2769 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2770 goto error;
2771 }
2772 cur_arg++;
2773 pattern = args[cur_arg];
2774 }
2775 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2776 if (proto == TCPCHK_RULES_HTTP_CHK)
2777 goto bad_http_kw;
2778 if (type != TCPCHK_EXPECT_UNDEF) {
2779 memprintf(errmsg, "only on pattern expected");
2780 goto error;
2781 }
2782 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2783
2784 if (!*(args[cur_arg+1])) {
2785 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2786 goto error;
2787 }
2788 cur_arg++;
2789 pattern = args[cur_arg];
2790 }
2791 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2792 if (type != TCPCHK_EXPECT_UNDEF) {
2793 memprintf(errmsg, "only on pattern expected");
2794 goto error;
2795 }
2796 if (proto != TCPCHK_RULES_HTTP_CHK)
2797 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2798 else {
2799 if (*(args[cur_arg]) != 's')
2800 goto bad_http_kw;
2801 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2802 }
2803
2804 if (!*(args[cur_arg+1])) {
2805 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2806 goto error;
2807 }
2808 cur_arg++;
2809 pattern = args[cur_arg];
2810 }
2811 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
2812 if (proto != TCPCHK_RULES_HTTP_CHK)
2813 goto bad_tcp_kw;
2814 if (type != TCPCHK_EXPECT_UNDEF) {
2815 memprintf(errmsg, "only on pattern expected");
2816 goto error;
2817 }
2818 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
2819
2820 if (!*(args[cur_arg+1])) {
2821 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2822 goto error;
2823 }
2824 cur_arg++;
2825 pattern = args[cur_arg];
2826 }
2827 else if (strcmp(args[cur_arg], "custom") == 0) {
2828 if (in_pattern) {
2829 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2830 goto error;
2831 }
2832 if (type != TCPCHK_EXPECT_UNDEF) {
2833 memprintf(errmsg, "only on pattern expected");
2834 goto error;
2835 }
2836 type = TCPCHK_EXPECT_CUSTOM;
2837 }
2838 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
2839 int orig_arg = cur_arg;
2840
2841 if (proto != TCPCHK_RULES_HTTP_CHK)
2842 goto bad_tcp_kw;
2843 if (type != TCPCHK_EXPECT_UNDEF) {
2844 memprintf(errmsg, "only on pattern expected");
2845 goto error;
2846 }
2847 type = TCPCHK_EXPECT_HTTP_HEADER;
2848
2849 if (strcmp(args[cur_arg], "fhdr") == 0)
2850 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
2851
2852 /* Parse the name pattern, mandatory */
2853 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
2854 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
2855 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
2856 args[orig_arg]);
2857 goto error;
2858 }
2859
2860 if (strcmp(args[cur_arg+1], "name-lf") == 0)
2861 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
2862
2863 cur_arg += 2;
2864 if (strcmp(args[cur_arg], "-m") == 0) {
2865 if (!*(args[cur_arg+1])) {
2866 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
2867 args[orig_arg], args[cur_arg]);
2868 goto error;
2869 }
2870 if (strcmp(args[cur_arg+1], "str") == 0)
2871 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
2872 else if (strcmp(args[cur_arg+1], "beg") == 0)
2873 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
2874 else if (strcmp(args[cur_arg+1], "end") == 0)
2875 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
2876 else if (strcmp(args[cur_arg+1], "sub") == 0)
2877 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
2878 else if (strcmp(args[cur_arg+1], "reg") == 0) {
2879 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
2880 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
2881 args[orig_arg]);
2882 goto error;
2883 }
2884 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
2885 }
2886 else {
2887 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
2888 args[orig_arg], args[cur_arg], args[cur_arg+1]);
2889 goto error;
2890 }
2891 cur_arg += 2;
2892 }
2893 else
2894 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
2895 npat = args[cur_arg];
2896
2897 if (!*(args[cur_arg+1]) ||
2898 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
2899 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
2900 goto next;
2901 }
2902 if (strcmp(args[cur_arg+1], "value-lf") == 0)
2903 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
2904
2905 /* Parse the value pattern, optional */
2906 if (strcmp(args[cur_arg+2], "-m") == 0) {
2907 cur_arg += 2;
2908 if (!*(args[cur_arg+1])) {
2909 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
2910 args[orig_arg], args[cur_arg]);
2911 goto error;
2912 }
2913 if (strcmp(args[cur_arg+1], "str") == 0)
2914 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
2915 else if (strcmp(args[cur_arg+1], "beg") == 0)
2916 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
2917 else if (strcmp(args[cur_arg+1], "end") == 0)
2918 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
2919 else if (strcmp(args[cur_arg+1], "sub") == 0)
2920 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
2921 else if (strcmp(args[cur_arg+1], "reg") == 0) {
2922 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
2923 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
2924 args[orig_arg]);
2925 goto error;
2926 }
2927 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
2928 }
2929 else {
2930 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
2931 args[orig_arg], args[cur_arg], args[cur_arg+1]);
2932 goto error;
2933 }
2934 }
2935 else
2936 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
2937
2938 if (!*(args[cur_arg+2])) {
2939 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
2940 goto error;
2941 }
2942 vpat = args[cur_arg+2];
2943 cur_arg += 2;
2944 }
2945 else if (strcmp(args[cur_arg], "comment") == 0) {
2946 if (in_pattern) {
2947 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2948 goto error;
2949 }
2950 if (!*(args[cur_arg+1])) {
2951 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
2952 goto error;
2953 }
2954 cur_arg++;
2955 free(comment);
2956 comment = strdup(args[cur_arg]);
2957 if (!comment) {
2958 memprintf(errmsg, "out of memory");
2959 goto error;
2960 }
2961 }
2962 else if (strcmp(args[cur_arg], "on-success") == 0) {
2963 if (in_pattern) {
2964 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2965 goto error;
2966 }
2967 if (!*(args[cur_arg+1])) {
2968 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
2969 goto error;
2970 }
2971 cur_arg++;
2972 on_success_msg = args[cur_arg];
2973 }
2974 else if (strcmp(args[cur_arg], "on-error") == 0) {
2975 if (in_pattern) {
2976 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2977 goto error;
2978 }
2979 if (!*(args[cur_arg+1])) {
2980 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
2981 goto error;
2982 }
2983 cur_arg++;
2984 on_error_msg = args[cur_arg];
2985 }
2986 else if (strcmp(args[cur_arg], "ok-status") == 0) {
2987 if (in_pattern) {
2988 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2989 goto error;
2990 }
2991 if (!*(args[cur_arg+1])) {
2992 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
2993 goto error;
2994 }
2995 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
2996 ok_st = HCHK_STATUS_L7OKD;
2997 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
2998 ok_st = HCHK_STATUS_L7OKCD;
2999 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3000 ok_st = HCHK_STATUS_L6OK;
3001 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3002 ok_st = HCHK_STATUS_L4OK;
3003 else {
3004 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3005 args[cur_arg], args[cur_arg+1]);
3006 goto error;
3007 }
3008 cur_arg++;
3009 }
3010 else if (strcmp(args[cur_arg], "error-status") == 0) {
3011 if (in_pattern) {
3012 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3013 goto error;
3014 }
3015 if (!*(args[cur_arg+1])) {
3016 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3017 goto error;
3018 }
3019 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3020 err_st = HCHK_STATUS_L7RSP;
3021 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3022 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003023 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3024 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003025 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3026 err_st = HCHK_STATUS_L6RSP;
3027 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3028 err_st = HCHK_STATUS_L4CON;
3029 else {
3030 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3031 args[cur_arg], args[cur_arg+1]);
3032 goto error;
3033 }
3034 cur_arg++;
3035 }
3036 else if (strcmp(args[cur_arg], "status-code") == 0) {
3037 int idx = 0;
3038
3039 if (in_pattern) {
3040 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3041 goto error;
3042 }
3043 if (!*(args[cur_arg+1])) {
3044 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3045 goto error;
3046 }
3047
3048 cur_arg++;
3049 release_sample_expr(status_expr);
3050 px->conf.args.ctx = ARGC_SRV;
3051 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
3052 file, line, errmsg, &px->conf.args, NULL);
3053 if (!status_expr) {
3054 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3055 goto error;
3056 }
3057 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3058 memprintf(errmsg, "error detected while parsing status-code expression : "
3059 " fetch method '%s' extracts information from '%s', "
3060 "none of which is available here.\n",
3061 args[cur_arg], sample_src_names(status_expr->fetch->use));
3062 goto error;
3063 }
3064 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3065 }
3066 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3067 if (in_pattern) {
3068 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3069 goto error;
3070 }
3071 if (!*(args[cur_arg+1])) {
3072 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3073 goto error;
3074 }
3075 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3076 tout_st = HCHK_STATUS_L7TOUT;
3077 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3078 tout_st = HCHK_STATUS_L6TOUT;
3079 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3080 tout_st = HCHK_STATUS_L4TOUT;
3081 else {
3082 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3083 args[cur_arg], args[cur_arg+1]);
3084 goto error;
3085 }
3086 cur_arg++;
3087 }
3088 else {
3089 if (proto == TCPCHK_RULES_HTTP_CHK) {
3090 bad_http_kw:
3091 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3092 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3093 }
3094 else {
3095 bad_tcp_kw:
3096 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3097 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3098 }
3099 goto error;
3100 }
3101 next:
3102 cur_arg++;
3103 }
3104
3105 chk = calloc(1, sizeof(*chk));
3106 if (!chk) {
3107 memprintf(errmsg, "out of memory");
3108 goto error;
3109 }
3110 chk->action = TCPCHK_ACT_EXPECT;
3111 LIST_INIT(&chk->expect.onerror_fmt);
3112 LIST_INIT(&chk->expect.onsuccess_fmt);
3113 chk->comment = comment; comment = NULL;
3114 chk->expect.type = type;
3115 chk->expect.min_recv = min_recv;
3116 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3117 chk->expect.ok_status = ok_st;
3118 chk->expect.err_status = err_st;
3119 chk->expect.tout_status = tout_st;
3120 chk->expect.status_expr = status_expr; status_expr = NULL;
3121
3122 if (on_success_msg) {
3123 px->conf.args.ctx = ARGC_SRV;
3124 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3125 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3126 goto error;
3127 }
3128 }
3129 if (on_error_msg) {
3130 px->conf.args.ctx = ARGC_SRV;
3131 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3132 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3133 goto error;
3134 }
3135 }
3136
3137 switch (chk->expect.type) {
3138 case TCPCHK_EXPECT_HTTP_STATUS: {
3139 const char *p = pattern;
3140 unsigned int c1,c2;
3141
3142 chk->expect.codes.codes = NULL;
3143 chk->expect.codes.num = 0;
3144 while (1) {
3145 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3146 if (*p == '-') {
3147 p++;
3148 c2 = read_uint(&p, pattern + strlen(pattern));
3149 }
3150 if (c1 > c2) {
3151 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3152 goto error;
3153 }
3154
3155 chk->expect.codes.num++;
3156 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3157 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3158 if (!chk->expect.codes.codes) {
3159 memprintf(errmsg, "out of memory");
3160 goto error;
3161 }
3162 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3163 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3164
3165 if (*p == '\0')
3166 break;
3167 if (*p != ',') {
3168 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3169 goto error;
3170 }
3171 p++;
3172 }
3173 break;
3174 }
3175 case TCPCHK_EXPECT_STRING:
3176 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003177 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003178 if (!isttest(chk->expect.data)) {
3179 memprintf(errmsg, "out of memory");
3180 goto error;
3181 }
3182 break;
3183 case TCPCHK_EXPECT_BINARY: {
3184 int len = chk->expect.data.len;
3185
3186 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3187 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3188 goto error;
3189 }
3190 chk->expect.data.len = len;
3191 break;
3192 }
3193 case TCPCHK_EXPECT_STRING_REGEX:
3194 case TCPCHK_EXPECT_BINARY_REGEX:
3195 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3196 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3197 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3198 if (!chk->expect.regex)
3199 goto error;
3200 break;
3201
3202 case TCPCHK_EXPECT_STRING_LF:
3203 case TCPCHK_EXPECT_BINARY_LF:
3204 case TCPCHK_EXPECT_HTTP_BODY_LF:
3205 LIST_INIT(&chk->expect.fmt);
3206 px->conf.args.ctx = ARGC_SRV;
3207 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3208 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3209 goto error;
3210 }
3211 break;
3212
3213 case TCPCHK_EXPECT_HTTP_HEADER:
3214 if (!npat) {
3215 memprintf(errmsg, "unexpected error, undefined header name pattern");
3216 goto error;
3217 }
3218 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3219 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3220 if (!chk->expect.hdr.name_re)
3221 goto error;
3222 }
3223 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3224 px->conf.args.ctx = ARGC_SRV;
3225 LIST_INIT(&chk->expect.hdr.name_fmt);
3226 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3227 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3228 goto error;
3229 }
3230 }
3231 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003232 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003233 if (!isttest(chk->expect.hdr.name)) {
3234 memprintf(errmsg, "out of memory");
3235 goto error;
3236 }
3237 }
3238
3239 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3240 chk->expect.hdr.value = IST_NULL;
3241 break;
3242 }
3243
3244 if (!vpat) {
3245 memprintf(errmsg, "unexpected error, undefined header value pattern");
3246 goto error;
3247 }
3248 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3249 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3250 if (!chk->expect.hdr.value_re)
3251 goto error;
3252 }
3253 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3254 px->conf.args.ctx = ARGC_SRV;
3255 LIST_INIT(&chk->expect.hdr.value_fmt);
3256 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3257 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3258 goto error;
3259 }
3260 }
3261 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003262 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003263 if (!isttest(chk->expect.hdr.value)) {
3264 memprintf(errmsg, "out of memory");
3265 goto error;
3266 }
3267 }
3268
3269 break;
3270 case TCPCHK_EXPECT_CUSTOM:
3271 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3272 break;
3273 case TCPCHK_EXPECT_UNDEF:
3274 memprintf(errmsg, "pattern not found");
3275 goto error;
3276 }
3277
3278 /* All tcp-check expect points back to the first inverse expect rule in
3279 * a chain of one or more expect rule, potentially itself.
3280 */
3281 chk->expect.head = chk;
3282 list_for_each_entry_rev(prev_check, rules, list) {
3283 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3284 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3285 chk->expect.head = prev_check;
3286 continue;
3287 }
3288 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3289 break;
3290 }
3291 return chk;
3292
3293 error:
3294 free_tcpcheck(chk, 0);
3295 free(comment);
3296 release_sample_expr(status_expr);
3297 return NULL;
3298}
3299
3300/* Overwrites fields of the old http send rule with those of the new one. When
3301 * replaced, old values are freed and replaced by the new ones. New values are
3302 * not copied but transferred. At the end <new> should be empty and can be
3303 * safely released. This function never fails.
3304 */
3305void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3306{
3307 struct logformat_node *lf, *lfb;
3308 struct tcpcheck_http_hdr *hdr, *bhdr;
3309
3310
3311 if (new->send.http.meth.str.area) {
3312 free(old->send.http.meth.str.area);
3313 old->send.http.meth.meth = new->send.http.meth.meth;
3314 old->send.http.meth.str.area = new->send.http.meth.str.area;
3315 old->send.http.meth.str.data = new->send.http.meth.str.data;
3316 new->send.http.meth.str = BUF_NULL;
3317 }
3318
3319 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3320 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3321 istfree(&old->send.http.uri);
3322 else
3323 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3324 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3325 old->send.http.uri = new->send.http.uri;
3326 new->send.http.uri = IST_NULL;
3327 }
3328 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
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 LIST_INIT(&old->send.http.uri_fmt);
3335 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
3336 LIST_DEL(&lf->list);
3337 LIST_ADDQ(&old->send.http.uri_fmt, &lf->list);
3338 }
3339 }
3340
3341 if (isttest(new->send.http.vsn)) {
3342 istfree(&old->send.http.vsn);
3343 old->send.http.vsn = new->send.http.vsn;
3344 new->send.http.vsn = IST_NULL;
3345 }
3346
3347 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3348 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
3349 LIST_DEL(&hdr->list);
3350 LIST_ADDQ(&old->send.http.hdrs, &hdr->list);
3351 }
3352
3353 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3354 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3355 istfree(&old->send.http.body);
3356 else
3357 free_tcpcheck_fmt(&old->send.http.body_fmt);
3358 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3359 old->send.http.body = new->send.http.body;
3360 new->send.http.body = IST_NULL;
3361 }
3362 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
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 LIST_INIT(&old->send.http.body_fmt);
3369 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
3370 LIST_DEL(&lf->list);
3371 LIST_ADDQ(&old->send.http.body_fmt, &lf->list);
3372 }
3373 }
3374}
3375
3376/* Internal function used to add an http-check rule in a list during the config
3377 * parsing step. Depending on its type, and the previously inserted rules, a
3378 * specific action may be performed or an error may be reported. This functions
3379 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3380 * message.
3381 */
3382int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3383{
3384 struct tcpcheck_rule *r;
3385
3386 /* the implicit send rule coming from an "option httpchk" line must be
3387 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003388 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003389 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003390 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003391 * sure the ruleset remains valid.
3392 */
3393
3394 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3395 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3396 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3397 * following tests are performed :
3398 *
3399 * 1- If there is no such rule or if it is not a send rule, the implicit send
3400 * rule is pushed in front of the ruleset
3401 *
3402 * 2- If it is another implicit send rule, it is replaced with the new one.
3403 *
3404 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3405 * both, overwriting the old send rule (the explicit one) with info of the
3406 * new send rule (the implicit one).
3407 */
3408 r = get_first_tcpcheck_rule(rules);
3409 if (r && r->action == TCPCHK_ACT_CONNECT)
3410 r = get_next_tcpcheck_rule(rules, r);
3411 if (!r || r->action != TCPCHK_ACT_SEND)
3412 LIST_ADD(rules->list, &chk->list);
3413 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
3414 LIST_DEL(&r->list);
3415 free_tcpcheck(r, 0);
3416 LIST_ADD(rules->list, &chk->list);
3417 }
3418 else {
3419 tcpcheck_overwrite_send_http_rule(r, chk);
3420 free_tcpcheck(chk, 0);
3421 }
3422 }
3423 else {
3424 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3425 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3426 * with an existing implicit send rule, if any. At the end, if there is no error,
3427 * the rule is appended to the list.
3428 */
3429
3430 r = get_last_tcpcheck_rule(rules);
3431 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3432 /* no error */;
3433 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3434 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3435 chk->index+1);
3436 return 0;
3437 }
3438 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3439 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3440 chk->index+1);
3441 return 0;
3442 }
3443 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3444 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3445 chk->index+1);
3446 return 0;
3447 }
3448
3449 if (chk->action == TCPCHK_ACT_SEND) {
3450 r = get_first_tcpcheck_rule(rules);
3451 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3452 tcpcheck_overwrite_send_http_rule(r, chk);
3453 free_tcpcheck(chk, 0);
3454 LIST_DEL(&r->list);
3455 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3456 chk = r;
3457 }
3458 }
3459 LIST_ADDQ(rules->list, &chk->list);
3460 }
3461 return 1;
3462}
3463
3464/* Check tcp-check health-check configuration for the proxy <px>. */
3465static int check_proxy_tcpcheck(struct proxy *px)
3466{
3467 struct tcpcheck_rule *chk, *back;
3468 char *comment = NULL, *errmsg = NULL;
3469 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003470 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003471
3472 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3473 deinit_proxy_tcpcheck(px);
3474 goto out;
3475 }
3476
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003477 ha_free(&px->check_command);
3478 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003479
3480 if (!px->tcpcheck_rules.list) {
3481 ha_alert("config : proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
3482 ret |= ERR_ALERT | ERR_FATAL;
3483 goto out;
3484 }
3485
3486 /* HTTP ruleset only : */
3487 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3488 struct tcpcheck_rule *next;
3489
3490 /* move remaining implicit send rule from "option httpchk" line to the right place.
3491 * If such rule exists, it must be the first one. In this case, the rule is moved
3492 * after the first connect rule, if any. Otherwise, nothing is done.
3493 */
3494 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3495 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3496 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3497 if (next && next->action == TCPCHK_ACT_CONNECT) {
3498 LIST_DEL(&chk->list);
3499 LIST_ADD(&next->list, &chk->list);
3500 chk->index = next->index;
3501 }
3502 }
3503
3504 /* add implicit expect rule if the last one is a send. It is inherited from previous
3505 * versions where the http expect rule was optional. Now it is possible to chained
3506 * send/expect rules but the last expect may still be implicit.
3507 */
3508 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3509 if (chk && chk->action == TCPCHK_ACT_SEND) {
3510 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3511 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3512 px->conf.file, px->conf.line, &errmsg);
3513 if (!next) {
3514 ha_alert("config : proxy '%s': unable to add implicit http-check expect rule "
3515 "(%s).\n", px->id, errmsg);
3516 free(errmsg);
3517 ret |= ERR_ALERT | ERR_FATAL;
3518 goto out;
3519 }
3520 LIST_ADDQ(px->tcpcheck_rules.list, &next->list);
3521 next->index = chk->index;
3522 }
3523 }
3524
3525 /* For all ruleset: */
3526
3527 /* If there is no connect rule preceding all send / expect rules, an
3528 * implicit one is inserted before all others.
3529 */
3530 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3531 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3532 chk = calloc(1, sizeof(*chk));
3533 if (!chk) {
3534 ha_alert("config : proxy '%s': unable to add implicit tcp-check connect rule "
3535 "(out of memory).\n", px->id);
3536 ret |= ERR_ALERT | ERR_FATAL;
3537 goto out;
3538 }
3539 chk->action = TCPCHK_ACT_CONNECT;
3540 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
3541 LIST_ADD(px->tcpcheck_rules.list, &chk->list);
3542 }
3543
3544 /* Remove all comment rules. To do so, when a such rule is found, the
3545 * comment is assigned to the following rule(s).
3546 */
3547 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003548 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3549 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003550
3551 prev_action = chk->action;
3552 switch (chk->action) {
3553 case TCPCHK_ACT_COMMENT:
3554 free(comment);
3555 comment = chk->comment;
3556 LIST_DEL(&chk->list);
3557 free(chk);
3558 break;
3559 case TCPCHK_ACT_CONNECT:
3560 if (!chk->comment && comment)
3561 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003562 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003563 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003564 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003565 break;
3566 case TCPCHK_ACT_SEND:
3567 case TCPCHK_ACT_EXPECT:
3568 if (!chk->comment && comment)
3569 chk->comment = strdup(comment);
3570 break;
3571 }
3572 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003573 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003574
3575 out:
3576 return ret;
3577}
3578
3579void deinit_proxy_tcpcheck(struct proxy *px)
3580{
3581 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3582 px->tcpcheck_rules.flags = 0;
3583 px->tcpcheck_rules.list = NULL;
3584}
3585
3586static void deinit_tcpchecks()
3587{
3588 struct tcpcheck_ruleset *rs;
3589 struct tcpcheck_rule *r, *rb;
3590 struct ebpt_node *node, *next;
3591
3592 node = ebpt_first(&shared_tcpchecks);
3593 while (node) {
3594 next = ebpt_next(node);
3595 ebpt_delete(node);
3596 free(node->key);
3597 rs = container_of(node, typeof(*rs), node);
3598 list_for_each_entry_safe(r, rb, &rs->rules, list) {
3599 LIST_DEL(&r->list);
3600 free_tcpcheck(r, 0);
3601 }
3602 free(rs);
3603 node = next;
3604 }
3605}
3606
3607int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3608{
3609 struct tcpcheck_rule *tcpcheck, *prev_check;
3610 struct tcpcheck_expect *expect;
3611
3612 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
3613 return 0;
3614 memset(tcpcheck, 0, sizeof(*tcpcheck));
3615 tcpcheck->action = TCPCHK_ACT_EXPECT;
3616
3617 expect = &tcpcheck->expect;
3618 expect->type = TCPCHK_EXPECT_STRING;
3619 LIST_INIT(&expect->onerror_fmt);
3620 LIST_INIT(&expect->onsuccess_fmt);
3621 expect->ok_status = HCHK_STATUS_L7OKD;
3622 expect->err_status = HCHK_STATUS_L7RSP;
3623 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003624 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003625 if (!isttest(expect->data)) {
3626 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3627 return 0;
3628 }
3629
3630 /* All tcp-check expect points back to the first inverse expect rule
3631 * in a chain of one or more expect rule, potentially itself.
3632 */
3633 tcpcheck->expect.head = tcpcheck;
3634 list_for_each_entry_rev(prev_check, rules->list, list) {
3635 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3636 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3637 tcpcheck->expect.head = prev_check;
3638 continue;
3639 }
3640 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3641 break;
3642 }
3643 LIST_ADDQ(rules->list, &tcpcheck->list);
3644 return 1;
3645}
3646
3647int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3648{
3649 struct tcpcheck_rule *tcpcheck;
3650 struct tcpcheck_send *send;
3651 const char *in;
3652 char *dst;
3653 int i;
3654
3655 if ((tcpcheck = pool_alloc(pool_head_tcpcheck_rule)) == NULL)
3656 return 0;
3657 memset(tcpcheck, 0, sizeof(*tcpcheck));
3658 tcpcheck->action = TCPCHK_ACT_SEND;
3659
3660 send = &tcpcheck->send;
3661 send->type = TCPCHK_SEND_STRING;
3662
3663 for (i = 0; strs[i]; i++)
3664 send->data.len += strlen(strs[i]);
3665
3666 send->data.ptr = malloc(istlen(send->data) + 1);
3667 if (!isttest(send->data)) {
3668 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3669 return 0;
3670 }
3671
3672 dst = istptr(send->data);
3673 for (i = 0; strs[i]; i++)
3674 for (in = strs[i]; (*dst = *in++); dst++);
3675 *dst = 0;
3676
3677 LIST_ADDQ(rules->list, &tcpcheck->list);
3678 return 1;
3679}
3680
3681/* Parses the "tcp-check" proxy keyword */
3682static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003683 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003684 char **errmsg)
3685{
3686 struct tcpcheck_ruleset *rs = NULL;
3687 struct tcpcheck_rule *chk = NULL;
3688 int index, cur_arg, ret = 0;
3689
3690 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3691 ret = 1;
3692
3693 /* Deduce the ruleset name from the proxy info */
3694 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3695 ((curpx == defpx) ? "defaults" : curpx->id),
3696 curpx->conf.file, curpx->conf.line);
3697
3698 rs = find_tcpcheck_ruleset(b_orig(&trash));
3699 if (rs == NULL) {
3700 rs = create_tcpcheck_ruleset(b_orig(&trash));
3701 if (rs == NULL) {
3702 memprintf(errmsg, "out of memory.\n");
3703 goto error;
3704 }
3705 }
3706
3707 index = 0;
3708 if (!LIST_ISEMPTY(&rs->rules)) {
3709 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3710 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003711 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003712 }
3713
3714 cur_arg = 1;
3715 if (strcmp(args[cur_arg], "connect") == 0)
3716 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3717 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3718 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3719 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3720 else if (strcmp(args[cur_arg], "expect") == 0)
3721 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3722 else if (strcmp(args[cur_arg], "comment") == 0)
3723 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3724 else {
3725 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3726
3727 if (!kw) {
3728 action_kw_tcp_check_build_list(&trash);
3729 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3730 "%s%s. but got '%s'",
3731 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3732 goto error;
3733 }
3734 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3735 }
3736
3737 if (!chk) {
3738 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3739 goto error;
3740 }
3741 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3742
3743 /* No error: add the tcp-check rule in the list */
3744 chk->index = index;
3745 LIST_ADDQ(&rs->rules, &chk->list);
3746
3747 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3748 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3749 /* Use this ruleset if the proxy already has tcp-check enabled */
3750 curpx->tcpcheck_rules.list = &rs->rules;
3751 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3752 }
3753 else {
3754 /* mark this ruleset as unused for now */
3755 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3756 }
3757
3758 return ret;
3759
3760 error:
3761 free_tcpcheck(chk, 0);
3762 free_tcpcheck_ruleset(rs);
3763 return -1;
3764}
3765
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003766/* Parses the "http-check" proxy keyword */
3767static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003768 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003769 char **errmsg)
3770{
3771 struct tcpcheck_ruleset *rs = NULL;
3772 struct tcpcheck_rule *chk = NULL;
3773 int index, cur_arg, ret = 0;
3774
3775 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3776 ret = 1;
3777
3778 cur_arg = 1;
3779 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3780 /* enable a graceful server shutdown on an HTTP 404 response */
3781 curpx->options |= PR_O_DISABLE404;
3782 if (too_many_args(1, args, errmsg, NULL))
3783 goto error;
3784 goto out;
3785 }
3786 else if (strcmp(args[cur_arg], "send-state") == 0) {
3787 /* enable emission of the apparent state of a server in HTTP checks */
3788 curpx->options2 |= PR_O2_CHK_SNDST;
3789 if (too_many_args(1, args, errmsg, NULL))
3790 goto error;
3791 goto out;
3792 }
3793
3794 /* Deduce the ruleset name from the proxy info */
3795 chunk_printf(&trash, "*http-check-%s_%s-%d",
3796 ((curpx == defpx) ? "defaults" : curpx->id),
3797 curpx->conf.file, curpx->conf.line);
3798
3799 rs = find_tcpcheck_ruleset(b_orig(&trash));
3800 if (rs == NULL) {
3801 rs = create_tcpcheck_ruleset(b_orig(&trash));
3802 if (rs == NULL) {
3803 memprintf(errmsg, "out of memory.\n");
3804 goto error;
3805 }
3806 }
3807
3808 index = 0;
3809 if (!LIST_ISEMPTY(&rs->rules)) {
3810 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3811 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
3812 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003813 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003814 }
3815
3816 if (strcmp(args[cur_arg], "connect") == 0)
3817 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3818 else if (strcmp(args[cur_arg], "send") == 0)
3819 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3820 else if (strcmp(args[cur_arg], "expect") == 0)
3821 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
3822 file, line, errmsg);
3823 else if (strcmp(args[cur_arg], "comment") == 0)
3824 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3825 else {
3826 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3827
3828 if (!kw) {
3829 action_kw_tcp_check_build_list(&trash);
3830 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
3831 " 'send', 'expect'%s%s. but got '%s'",
3832 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3833 goto error;
3834 }
3835 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3836 }
3837
3838 if (!chk) {
3839 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3840 goto error;
3841 }
3842 ret = (*errmsg != NULL); /* Handle warning */
3843
3844 chk->index = index;
3845 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3846 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3847 /* Use this ruleset if the proxy already has http-check enabled */
3848 curpx->tcpcheck_rules.list = &rs->rules;
3849 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
3850 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
3851 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3852 curpx->tcpcheck_rules.list = NULL;
3853 goto error;
3854 }
3855 }
3856 else {
3857 /* mark this ruleset as unused for now */
3858 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
3859 LIST_ADDQ(&rs->rules, &chk->list);
3860 }
3861
3862 out:
3863 return ret;
3864
3865 error:
3866 free_tcpcheck(chk, 0);
3867 free_tcpcheck_ruleset(rs);
3868 return -1;
3869}
3870
3871/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01003872int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003873 const char *file, int line)
3874{
3875 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
3876 static char *redis_res = "+PONG\r\n";
3877
3878 struct tcpcheck_ruleset *rs = NULL;
3879 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
3880 struct tcpcheck_rule *chk;
3881 char *errmsg = NULL;
3882 int err_code = 0;
3883
3884 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
3885 err_code |= ERR_WARN;
3886
3887 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
3888 goto out;
3889
3890 curpx->options2 &= ~PR_O2_CHK_ANY;
3891 curpx->options2 |= PR_O2_TCPCHK_CHK;
3892
3893 free_tcpcheck_vars(&rules->preset_vars);
3894 rules->list = NULL;
3895 rules->flags = 0;
3896
3897 rs = find_tcpcheck_ruleset("*redis-check");
3898 if (rs)
3899 goto ruleset_found;
3900
3901 rs = create_tcpcheck_ruleset("*redis-check");
3902 if (rs == NULL) {
3903 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
3904 goto error;
3905 }
3906
3907 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
3908 1, curpx, &rs->rules, file, line, &errmsg);
3909 if (!chk) {
3910 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
3911 goto error;
3912 }
3913 chk->index = 0;
3914 LIST_ADDQ(&rs->rules, &chk->list);
3915
3916 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
3917 "error-status", "L7STS",
3918 "on-error", "%[res.payload(0,0),cut_crlf]",
3919 "on-success", "Redis server is ok",
3920 ""},
3921 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
3922 if (!chk) {
3923 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
3924 goto error;
3925 }
3926 chk->index = 1;
3927 LIST_ADDQ(&rs->rules, &chk->list);
3928
3929 ruleset_found:
3930 rules->list = &rs->rules;
3931 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
3932 rules->flags |= TCPCHK_RULES_REDIS_CHK;
3933
3934 out:
3935 free(errmsg);
3936 return err_code;
3937
3938 error:
3939 free_tcpcheck_ruleset(rs);
3940 err_code |= ERR_ALERT | ERR_FATAL;
3941 goto out;
3942}
3943
3944
3945/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01003946int 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 +01003947 const char *file, int line)
3948{
3949 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
3950 * ssl-hello-chk option to ensure that the remote server speaks SSL.
3951 *
3952 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
3953 */
3954 static char sslv3_client_hello[] = {
3955 "16" /* ContentType : 0x16 = Handshake */
3956 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
3957 "0079" /* ContentLength : 0x79 bytes after this one */
3958 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
3959 "000075" /* HandshakeLength : 0x75 bytes after this one */
3960 "0300" /* Hello Version : 0x0300 = v3 */
3961 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
3962 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
3963 "00" /* Session ID length : empty (no session ID) */
3964 "004E" /* Cipher Suite Length : 78 bytes after this one */
3965 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
3966 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
3967 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
3968 "000D" "000E" "000F" "0010" /* various bit lengths, */
3969 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
3970 "0015" "0016" "0017" "0018"
3971 "0019" "001A" "001B" "002F"
3972 "0030" "0031" "0032" "0033"
3973 "0034" "0035" "0036" "0037"
3974 "0038" "0039" "003A"
3975 "01" /* Compression Length : 0x01 = 1 byte for types */
3976 "00" /* Compression Type : 0x00 = NULL compression */
3977 };
3978
3979 struct tcpcheck_ruleset *rs = NULL;
3980 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
3981 struct tcpcheck_rule *chk;
3982 char *errmsg = NULL;
3983 int err_code = 0;
3984
3985 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
3986 err_code |= ERR_WARN;
3987
3988 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
3989 goto out;
3990
3991 curpx->options2 &= ~PR_O2_CHK_ANY;
3992 curpx->options2 |= PR_O2_TCPCHK_CHK;
3993
3994 free_tcpcheck_vars(&rules->preset_vars);
3995 rules->list = NULL;
3996 rules->flags = 0;
3997
3998 rs = find_tcpcheck_ruleset("*ssl-hello-check");
3999 if (rs)
4000 goto ruleset_found;
4001
4002 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4003 if (rs == NULL) {
4004 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4005 goto error;
4006 }
4007
4008 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4009 1, curpx, &rs->rules, file, line, &errmsg);
4010 if (!chk) {
4011 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4012 goto error;
4013 }
4014 chk->index = 0;
4015 LIST_ADDQ(&rs->rules, &chk->list);
4016
4017 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4018 "min-recv", "5", "ok-status", "L6OK",
4019 "error-status", "L6RSP", "tout-status", "L6TOUT",
4020 ""},
4021 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4022 if (!chk) {
4023 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4024 goto error;
4025 }
4026 chk->index = 1;
4027 LIST_ADDQ(&rs->rules, &chk->list);
4028
4029 ruleset_found:
4030 rules->list = &rs->rules;
4031 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4032 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4033
4034 out:
4035 free(errmsg);
4036 return err_code;
4037
4038 error:
4039 free_tcpcheck_ruleset(rs);
4040 err_code |= ERR_ALERT | ERR_FATAL;
4041 goto out;
4042}
4043
4044/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004045int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004046 const char *file, int line)
4047{
4048 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4049
4050 struct tcpcheck_ruleset *rs = NULL;
4051 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4052 struct tcpcheck_rule *chk;
4053 struct tcpcheck_var *var = NULL;
4054 char *cmd = NULL, *errmsg = NULL;
4055 int err_code = 0;
4056
4057 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4058 err_code |= ERR_WARN;
4059
4060 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4061 goto out;
4062
4063 curpx->options2 &= ~PR_O2_CHK_ANY;
4064 curpx->options2 |= PR_O2_TCPCHK_CHK;
4065
4066 free_tcpcheck_vars(&rules->preset_vars);
4067 rules->list = NULL;
4068 rules->flags = 0;
4069
4070 cur_arg += 2;
4071 if (*args[cur_arg] && *args[cur_arg+1] &&
4072 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4073 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4074 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4075 if (cmd)
4076 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4077 }
4078 else {
4079 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4080 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4081 cmd = strdup("HELO localhost");
4082 }
4083
4084 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4085 if (cmd == NULL || var == NULL) {
4086 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4087 goto error;
4088 }
4089 var->data.type = SMP_T_STR;
4090 var->data.u.str.area = cmd;
4091 var->data.u.str.data = strlen(cmd);
4092 LIST_INIT(&var->list);
4093 LIST_ADDQ(&rules->preset_vars, &var->list);
4094 cmd = NULL;
4095 var = NULL;
4096
4097 rs = find_tcpcheck_ruleset("*smtp-check");
4098 if (rs)
4099 goto ruleset_found;
4100
4101 rs = create_tcpcheck_ruleset("*smtp-check");
4102 if (rs == NULL) {
4103 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4104 goto error;
4105 }
4106
4107 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4108 1, curpx, &rs->rules, file, line, &errmsg);
4109 if (!chk) {
4110 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4111 goto error;
4112 }
4113 chk->index = 0;
4114 LIST_ADDQ(&rs->rules, &chk->list);
4115
4116 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4117 "min-recv", "4",
4118 "error-status", "L7RSP",
4119 "on-error", "%[res.payload(0,0),cut_crlf]",
4120 ""},
4121 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4122 if (!chk) {
4123 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4124 goto error;
4125 }
4126 chk->index = 1;
4127 LIST_ADDQ(&rs->rules, &chk->list);
4128
4129 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4130 "min-recv", "4",
4131 "error-status", "L7STS",
4132 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4133 "status-code", "res.payload(0,3)",
4134 ""},
4135 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4136 if (!chk) {
4137 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4138 goto error;
4139 }
4140 chk->index = 2;
4141 LIST_ADDQ(&rs->rules, &chk->list);
4142
4143 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4144 1, curpx, &rs->rules, file, line, &errmsg);
4145 if (!chk) {
4146 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4147 goto error;
4148 }
4149 chk->index = 3;
4150 LIST_ADDQ(&rs->rules, &chk->list);
4151
4152 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4153 "min-recv", "4",
4154 "error-status", "L7STS",
4155 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4156 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4157 "status-code", "res.payload(0,3)",
4158 ""},
4159 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4160 if (!chk) {
4161 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4162 goto error;
4163 }
4164 chk->index = 4;
4165 LIST_ADDQ(&rs->rules, &chk->list);
4166
4167 ruleset_found:
4168 rules->list = &rs->rules;
4169 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4170 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4171
4172 out:
4173 free(errmsg);
4174 return err_code;
4175
4176 error:
4177 free(cmd);
4178 free(var);
4179 free_tcpcheck_vars(&rules->preset_vars);
4180 free_tcpcheck_ruleset(rs);
4181 err_code |= ERR_ALERT | ERR_FATAL;
4182 goto out;
4183}
4184
4185/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004186int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004187 const char *file, int line)
4188{
4189 static char pgsql_req[] = {
4190 "%[var(check.plen),htonl,hex]" /* The packet length*/
4191 "00030000" /* the version 3.0 */
4192 "7573657200" /* "user" key */
4193 "%[var(check.username),hex]00" /* the username */
4194 "00"
4195 };
4196
4197 struct tcpcheck_ruleset *rs = NULL;
4198 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4199 struct tcpcheck_rule *chk;
4200 struct tcpcheck_var *var = NULL;
4201 char *user = NULL, *errmsg = NULL;
4202 size_t packetlen = 0;
4203 int err_code = 0;
4204
4205 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4206 err_code |= ERR_WARN;
4207
4208 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4209 goto out;
4210
4211 curpx->options2 &= ~PR_O2_CHK_ANY;
4212 curpx->options2 |= PR_O2_TCPCHK_CHK;
4213
4214 free_tcpcheck_vars(&rules->preset_vars);
4215 rules->list = NULL;
4216 rules->flags = 0;
4217
4218 cur_arg += 2;
4219 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4220 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4221 file, line, args[0], args[1]);
4222 goto error;
4223 }
4224 if (strcmp(args[cur_arg], "user") == 0) {
4225 packetlen = 15 + strlen(args[cur_arg+1]);
4226 user = strdup(args[cur_arg+1]);
4227
4228 var = create_tcpcheck_var(ist("check.username"));
4229 if (user == NULL || var == NULL) {
4230 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4231 goto error;
4232 }
4233 var->data.type = SMP_T_STR;
4234 var->data.u.str.area = user;
4235 var->data.u.str.data = strlen(user);
4236 LIST_INIT(&var->list);
4237 LIST_ADDQ(&rules->preset_vars, &var->list);
4238 user = NULL;
4239 var = NULL;
4240
4241 var = create_tcpcheck_var(ist("check.plen"));
4242 if (var == NULL) {
4243 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4244 goto error;
4245 }
4246 var->data.type = SMP_T_SINT;
4247 var->data.u.sint = packetlen;
4248 LIST_INIT(&var->list);
4249 LIST_ADDQ(&rules->preset_vars, &var->list);
4250 var = NULL;
4251 }
4252 else {
4253 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4254 file, line, args[0], args[1]);
4255 goto error;
4256 }
4257
4258 rs = find_tcpcheck_ruleset("*pgsql-check");
4259 if (rs)
4260 goto ruleset_found;
4261
4262 rs = create_tcpcheck_ruleset("*pgsql-check");
4263 if (rs == NULL) {
4264 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4265 goto error;
4266 }
4267
4268 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4269 1, curpx, &rs->rules, file, line, &errmsg);
4270 if (!chk) {
4271 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4272 goto error;
4273 }
4274 chk->index = 0;
4275 LIST_ADDQ(&rs->rules, &chk->list);
4276
4277 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
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 = 1;
4284 LIST_ADDQ(&rs->rules, &chk->list);
4285
4286 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4287 "min-recv", "5",
4288 "error-status", "L7RSP",
4289 "on-error", "%[res.payload(6,0)]",
4290 ""},
4291 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4292 if (!chk) {
4293 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4294 goto error;
4295 }
4296 chk->index = 2;
4297 LIST_ADDQ(&rs->rules, &chk->list);
4298
4299 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4300 "min-recv", "9",
4301 "error-status", "L7STS",
4302 "on-success", "PostgreSQL server is ok",
4303 "on-error", "PostgreSQL unknown error",
4304 ""},
4305 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4306 if (!chk) {
4307 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4308 goto error;
4309 }
4310 chk->index = 3;
4311 LIST_ADDQ(&rs->rules, &chk->list);
4312
4313 ruleset_found:
4314 rules->list = &rs->rules;
4315 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4316 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4317
4318 out:
4319 free(errmsg);
4320 return err_code;
4321
4322 error:
4323 free(user);
4324 free(var);
4325 free_tcpcheck_vars(&rules->preset_vars);
4326 free_tcpcheck_ruleset(rs);
4327 err_code |= ERR_ALERT | ERR_FATAL;
4328 goto out;
4329}
4330
4331
4332/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004333int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004334 const char *file, int line)
4335{
4336 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4337 * const char mysql40_client_auth_pkt[] = {
4338 * "\x0e\x00\x00" // packet length
4339 * "\x01" // packet number
4340 * "\x00\x00" // client capabilities
4341 * "\x00\x00\x01" // max packet
4342 * "haproxy\x00" // username (null terminated string)
4343 * "\x00" // filler (always 0x00)
4344 * "\x01\x00\x00" // packet length
4345 * "\x00" // packet number
4346 * "\x01" // COM_QUIT command
4347 * };
4348 */
4349 static char mysql40_rsname[] = "*mysql40-check";
4350 static char mysql40_req[] = {
4351 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4352 "0080" /* client capabilities */
4353 "000001" /* max packet */
4354 "%[var(check.username),hex]00" /* the username */
4355 "00" /* filler (always 0x00) */
4356 "010000" /* packet length*/
4357 "00" /* sequence ID */
4358 "01" /* COM_QUIT command */
4359 };
4360
4361 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4362 * const char mysql41_client_auth_pkt[] = {
4363 * "\x0e\x00\x00\" // packet length
4364 * "\x01" // packet number
4365 * "\x00\x00\x00\x00" // client capabilities
4366 * "\x00\x00\x00\x01" // max packet
4367 * "\x21" // character set (UTF-8)
4368 * char[23] // All zeroes
4369 * "haproxy\x00" // username (null terminated string)
4370 * "\x00" // filler (always 0x00)
4371 * "\x01\x00\x00" // packet length
4372 * "\x00" // packet number
4373 * "\x01" // COM_QUIT command
4374 * };
4375 */
4376 static char mysql41_rsname[] = "*mysql41-check";
4377 static char mysql41_req[] = {
4378 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4379 "00820000" /* client capabilities */
4380 "00800001" /* max packet */
4381 "21" /* character set (UTF-8) */
4382 "000000000000000000000000" /* 23 bytes, al zeroes */
4383 "0000000000000000000000"
4384 "%[var(check.username),hex]00" /* the username */
4385 "00" /* filler (always 0x00) */
4386 "010000" /* packet length*/
4387 "00" /* sequence ID */
4388 "01" /* COM_QUIT command */
4389 };
4390
4391 struct tcpcheck_ruleset *rs = NULL;
4392 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4393 struct tcpcheck_rule *chk;
4394 struct tcpcheck_var *var = NULL;
4395 char *mysql_rsname = "*mysql-check";
4396 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4397 int index = 0, err_code = 0;
4398
4399 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4400 err_code |= ERR_WARN;
4401
4402 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4403 goto out;
4404
4405 curpx->options2 &= ~PR_O2_CHK_ANY;
4406 curpx->options2 |= PR_O2_TCPCHK_CHK;
4407
4408 free_tcpcheck_vars(&rules->preset_vars);
4409 rules->list = NULL;
4410 rules->flags = 0;
4411
4412 cur_arg += 2;
4413 if (*args[cur_arg]) {
4414 int packetlen, userlen;
4415
4416 if (strcmp(args[cur_arg], "user") != 0) {
4417 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4418 file, line, args[0], args[1], args[cur_arg]);
4419 goto error;
4420 }
4421
4422 if (*(args[cur_arg+1]) == 0) {
4423 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4424 file, line, args[0], args[1], args[cur_arg]);
4425 goto error;
4426 }
4427
4428 hdr = calloc(4, sizeof(*hdr));
4429 user = strdup(args[cur_arg+1]);
4430 userlen = strlen(args[cur_arg+1]);
4431
4432 if (hdr == NULL || user == NULL) {
4433 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4434 goto error;
4435 }
4436
4437 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4438 packetlen = userlen + 7 + 27;
4439 mysql_req = mysql41_req;
4440 mysql_rsname = mysql41_rsname;
4441 }
4442 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4443 packetlen = userlen + 7;
4444 mysql_req = mysql40_req;
4445 mysql_rsname = mysql40_rsname;
4446 }
4447 else {
4448 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4449 file, line, args[cur_arg], args[cur_arg+2]);
4450 goto error;
4451 }
4452
4453 hdr[0] = (unsigned char)(packetlen & 0xff);
4454 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4455 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4456 hdr[3] = 1;
4457
4458 var = create_tcpcheck_var(ist("check.header"));
4459 if (var == NULL) {
4460 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4461 goto error;
4462 }
4463 var->data.type = SMP_T_STR;
4464 var->data.u.str.area = hdr;
4465 var->data.u.str.data = 4;
4466 LIST_INIT(&var->list);
4467 LIST_ADDQ(&rules->preset_vars, &var->list);
4468 hdr = NULL;
4469 var = NULL;
4470
4471 var = create_tcpcheck_var(ist("check.username"));
4472 if (var == NULL) {
4473 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4474 goto error;
4475 }
4476 var->data.type = SMP_T_STR;
4477 var->data.u.str.area = user;
4478 var->data.u.str.data = strlen(user);
4479 LIST_INIT(&var->list);
4480 LIST_ADDQ(&rules->preset_vars, &var->list);
4481 user = NULL;
4482 var = NULL;
4483 }
4484
4485 rs = find_tcpcheck_ruleset(mysql_rsname);
4486 if (rs)
4487 goto ruleset_found;
4488
4489 rs = create_tcpcheck_ruleset(mysql_rsname);
4490 if (rs == NULL) {
4491 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4492 goto error;
4493 }
4494
4495 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4496 1, curpx, &rs->rules, file, line, &errmsg);
4497 if (!chk) {
4498 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4499 goto error;
4500 }
4501 chk->index = index++;
4502 LIST_ADDQ(&rs->rules, &chk->list);
4503
4504 if (mysql_req) {
4505 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4506 1, curpx, &rs->rules, file, line, &errmsg);
4507 if (!chk) {
4508 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4509 goto error;
4510 }
4511 chk->index = index++;
4512 LIST_ADDQ(&rs->rules, &chk->list);
4513 }
4514
4515 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4516 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4517 if (!chk) {
4518 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4519 goto error;
4520 }
4521 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4522 chk->index = index++;
4523 LIST_ADDQ(&rs->rules, &chk->list);
4524
4525 if (mysql_req) {
4526 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4527 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4528 if (!chk) {
4529 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4530 goto error;
4531 }
4532 chk->expect.custom = tcpcheck_mysql_expect_ok;
4533 chk->index = index++;
4534 LIST_ADDQ(&rs->rules, &chk->list);
4535 }
4536
4537 ruleset_found:
4538 rules->list = &rs->rules;
4539 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4540 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4541
4542 out:
4543 free(errmsg);
4544 return err_code;
4545
4546 error:
4547 free(hdr);
4548 free(user);
4549 free(var);
4550 free_tcpcheck_vars(&rules->preset_vars);
4551 free_tcpcheck_ruleset(rs);
4552 err_code |= ERR_ALERT | ERR_FATAL;
4553 goto out;
4554}
4555
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004556int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004557 const char *file, int line)
4558{
4559 static char *ldap_req = "300C020101600702010304008000";
4560
4561 struct tcpcheck_ruleset *rs = NULL;
4562 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4563 struct tcpcheck_rule *chk;
4564 char *errmsg = NULL;
4565 int err_code = 0;
4566
4567 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4568 err_code |= ERR_WARN;
4569
4570 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4571 goto out;
4572
4573 curpx->options2 &= ~PR_O2_CHK_ANY;
4574 curpx->options2 |= PR_O2_TCPCHK_CHK;
4575
4576 free_tcpcheck_vars(&rules->preset_vars);
4577 rules->list = NULL;
4578 rules->flags = 0;
4579
4580 rs = find_tcpcheck_ruleset("*ldap-check");
4581 if (rs)
4582 goto ruleset_found;
4583
4584 rs = create_tcpcheck_ruleset("*ldap-check");
4585 if (rs == NULL) {
4586 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4587 goto error;
4588 }
4589
4590 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4591 1, curpx, &rs->rules, file, line, &errmsg);
4592 if (!chk) {
4593 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4594 goto error;
4595 }
4596 chk->index = 0;
4597 LIST_ADDQ(&rs->rules, &chk->list);
4598
4599 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4600 "min-recv", "14",
4601 "on-error", "Not LDAPv3 protocol",
4602 ""},
4603 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4604 if (!chk) {
4605 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4606 goto error;
4607 }
4608 chk->index = 1;
4609 LIST_ADDQ(&rs->rules, &chk->list);
4610
4611 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
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->expect.custom = tcpcheck_ldap_expect_bindrsp;
4618 chk->index = 2;
4619 LIST_ADDQ(&rs->rules, &chk->list);
4620
4621 ruleset_found:
4622 rules->list = &rs->rules;
4623 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4624 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4625
4626 out:
4627 free(errmsg);
4628 return err_code;
4629
4630 error:
4631 free_tcpcheck_ruleset(rs);
4632 err_code |= ERR_ALERT | ERR_FATAL;
4633 goto out;
4634}
4635
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004636int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004637 const char *file, int line)
4638{
4639 struct tcpcheck_ruleset *rs = NULL;
4640 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4641 struct tcpcheck_rule *chk;
4642 char *spop_req = NULL;
4643 char *errmsg = NULL;
4644 int spop_len = 0, err_code = 0;
4645
4646 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4647 err_code |= ERR_WARN;
4648
4649 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4650 goto out;
4651
4652 curpx->options2 &= ~PR_O2_CHK_ANY;
4653 curpx->options2 |= PR_O2_TCPCHK_CHK;
4654
4655 free_tcpcheck_vars(&rules->preset_vars);
4656 rules->list = NULL;
4657 rules->flags = 0;
4658
4659
4660 rs = find_tcpcheck_ruleset("*spop-check");
4661 if (rs)
4662 goto ruleset_found;
4663
4664 rs = create_tcpcheck_ruleset("*spop-check");
4665 if (rs == NULL) {
4666 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4667 goto error;
4668 }
4669
4670 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4671 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4672 goto error;
4673 }
4674 chunk_reset(&trash);
4675 dump_binary(&trash, spop_req, spop_len);
4676 trash.area[trash.data] = '\0';
4677
4678 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4679 1, curpx, &rs->rules, file, line, &errmsg);
4680 if (!chk) {
4681 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4682 goto error;
4683 }
4684 chk->index = 0;
4685 LIST_ADDQ(&rs->rules, &chk->list);
4686
4687 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4688 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4689 if (!chk) {
4690 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4691 goto error;
4692 }
4693 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4694 chk->index = 1;
4695 LIST_ADDQ(&rs->rules, &chk->list);
4696
4697 ruleset_found:
4698 rules->list = &rs->rules;
4699 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4700 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4701
4702 out:
4703 free(spop_req);
4704 free(errmsg);
4705 return err_code;
4706
4707 error:
4708 free_tcpcheck_ruleset(rs);
4709 err_code |= ERR_ALERT | ERR_FATAL;
4710 goto out;
4711}
4712
4713
4714static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4715{
4716 struct tcpcheck_rule *chk = NULL;
4717 struct tcpcheck_http_hdr *hdr = NULL;
4718 char *meth = NULL, *uri = NULL, *vsn = NULL;
4719 char *hdrs, *body;
4720
4721 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4722 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4723 if (hdrs == body)
4724 hdrs = NULL;
4725 if (hdrs) {
4726 *hdrs = '\0';
4727 hdrs +=2;
4728 }
4729 if (body) {
4730 *body = '\0';
4731 body += 4;
4732 }
4733 if (hdrs || body) {
4734 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4735 " Please, consider to use 'http-check send' directive instead.");
4736 }
4737
4738 chk = calloc(1, sizeof(*chk));
4739 if (!chk) {
4740 memprintf(errmsg, "out of memory");
4741 goto error;
4742 }
4743 chk->action = TCPCHK_ACT_SEND;
4744 chk->send.type = TCPCHK_SEND_HTTP;
4745 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4746 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4747 LIST_INIT(&chk->send.http.hdrs);
4748
4749 /* Copy the method, uri and version */
4750 if (*args[cur_arg]) {
4751 if (!*args[cur_arg+1])
4752 uri = args[cur_arg];
4753 else
4754 meth = args[cur_arg];
4755 }
4756 if (*args[cur_arg+1])
4757 uri = args[cur_arg+1];
4758 if (*args[cur_arg+2])
4759 vsn = args[cur_arg+2];
4760
4761 if (meth) {
4762 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4763 chk->send.http.meth.str.area = strdup(meth);
4764 chk->send.http.meth.str.data = strlen(meth);
4765 if (!chk->send.http.meth.str.area) {
4766 memprintf(errmsg, "out of memory");
4767 goto error;
4768 }
4769 }
4770 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004771 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004772 if (!isttest(chk->send.http.uri)) {
4773 memprintf(errmsg, "out of memory");
4774 goto error;
4775 }
4776 }
4777 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004778 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004779 if (!isttest(chk->send.http.vsn)) {
4780 memprintf(errmsg, "out of memory");
4781 goto error;
4782 }
4783 }
4784
4785 /* Copy the header */
4786 if (hdrs) {
4787 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4788 struct h1m h1m;
4789 int i, ret;
4790
4791 /* Build and parse the request */
4792 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4793
4794 h1m.flags = H1_MF_HDRS_ONLY;
4795 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4796 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4797 &h1m, NULL);
4798 if (ret <= 0) {
4799 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4800 goto error;
4801 }
4802
4803 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4804 hdr = calloc(1, sizeof(*hdr));
4805 if (!hdr) {
4806 memprintf(errmsg, "out of memory");
4807 goto error;
4808 }
4809 LIST_INIT(&hdr->value);
4810 hdr->name = istdup(tmp_hdrs[i].n);
4811 if (!hdr->name.ptr) {
4812 memprintf(errmsg, "out of memory");
4813 goto error;
4814 }
4815
4816 ist0(tmp_hdrs[i].v);
4817 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
4818 goto error;
4819 LIST_ADDQ(&chk->send.http.hdrs, &hdr->list);
4820 }
4821 }
4822
4823 /* Copy the body */
4824 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004825 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004826 if (!isttest(chk->send.http.body)) {
4827 memprintf(errmsg, "out of memory");
4828 goto error;
4829 }
4830 }
4831
4832 return chk;
4833
4834 error:
4835 free_tcpcheck_http_hdr(hdr);
4836 free_tcpcheck(chk, 0);
4837 return NULL;
4838}
4839
4840/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004841int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004842 const char *file, int line)
4843{
4844 struct tcpcheck_ruleset *rs = NULL;
4845 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4846 struct tcpcheck_rule *chk;
4847 char *errmsg = NULL;
4848 int err_code = 0;
4849
4850 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4851 err_code |= ERR_WARN;
4852
4853 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4854 goto out;
4855
4856 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
4857 if (!chk) {
4858 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
4859 goto error;
4860 }
4861 if (errmsg) {
4862 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
4863 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01004864 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004865 }
4866
4867 no_request:
4868 curpx->options2 &= ~PR_O2_CHK_ANY;
4869 curpx->options2 |= PR_O2_TCPCHK_CHK;
4870
4871 free_tcpcheck_vars(&rules->preset_vars);
4872 rules->list = NULL;
4873 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
4874
4875 /* Deduce the ruleset name from the proxy info */
4876 chunk_printf(&trash, "*http-check-%s_%s-%d",
4877 ((curpx == defpx) ? "defaults" : curpx->id),
4878 curpx->conf.file, curpx->conf.line);
4879
4880 rs = find_tcpcheck_ruleset(b_orig(&trash));
4881 if (rs == NULL) {
4882 rs = create_tcpcheck_ruleset(b_orig(&trash));
4883 if (rs == NULL) {
4884 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4885 goto error;
4886 }
4887 }
4888
4889 rules->list = &rs->rules;
4890 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4891 rules->flags |= TCPCHK_RULES_HTTP_CHK;
4892 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
4893 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
4894 rules->list = NULL;
4895 goto error;
4896 }
4897
4898 out:
4899 free(errmsg);
4900 return err_code;
4901
4902 error:
4903 free_tcpcheck_ruleset(rs);
4904 free_tcpcheck(chk, 0);
4905 err_code |= ERR_ALERT | ERR_FATAL;
4906 goto out;
4907}
4908
4909/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004910int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004911 const char *file, int line)
4912{
4913 struct tcpcheck_ruleset *rs = NULL;
4914 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4915 int err_code = 0;
4916
4917 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4918 err_code |= ERR_WARN;
4919
4920 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4921 goto out;
4922
4923 curpx->options2 &= ~PR_O2_CHK_ANY;
4924 curpx->options2 |= PR_O2_TCPCHK_CHK;
4925
4926 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
4927 /* If a tcp-check rulesset is already set, do nothing */
4928 if (rules->list)
4929 goto out;
4930
4931 /* If a tcp-check ruleset is waiting to be used for the current proxy,
4932 * get it.
4933 */
4934 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
4935 goto curpx_ruleset;
4936
4937 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
4938 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
4939 rs = find_tcpcheck_ruleset(b_orig(&trash));
4940 if (rs)
4941 goto ruleset_found;
4942 }
4943
4944 curpx_ruleset:
4945 /* Deduce the ruleset name from the proxy info */
4946 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
4947 ((curpx == defpx) ? "defaults" : curpx->id),
4948 curpx->conf.file, curpx->conf.line);
4949
4950 rs = find_tcpcheck_ruleset(b_orig(&trash));
4951 if (rs == NULL) {
4952 rs = create_tcpcheck_ruleset(b_orig(&trash));
4953 if (rs == NULL) {
4954 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4955 goto error;
4956 }
4957 }
4958
4959 ruleset_found:
4960 free_tcpcheck_vars(&rules->preset_vars);
4961 rules->list = &rs->rules;
4962 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4963 rules->flags |= TCPCHK_RULES_TCP_CHK;
4964
4965 out:
4966 return err_code;
4967
4968 error:
4969 err_code |= ERR_ALERT | ERR_FATAL;
4970 goto out;
4971}
4972
Willy Tarreau51cd5952020-06-05 12:25:38 +02004973static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004974 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02004975 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
4976 { 0, NULL, NULL },
4977}};
4978
4979REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
4980REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
4981REGISTER_POST_DEINIT(deinit_tcpchecks);
4982INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);