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