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