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