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