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