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