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