blob: 5fec53e2184fe6bf016844e9dba5ea106f0caad9 [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>
Christopher Faulet1329f2a2021-12-16 17:32:56 +010042#include <haproxy/conn_stream.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020043#include <haproxy/errors.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020044#include <haproxy/global.h>
45#include <haproxy/h1.h>
46#include <haproxy/http.h>
47#include <haproxy/http_htx.h>
48#include <haproxy/htx.h>
49#include <haproxy/istbuf.h>
50#include <haproxy/list.h>
51#include <haproxy/log.h>
Christopher Faulet8a0e5f82021-09-16 16:01:09 +020052#include <haproxy/net_helper.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020053#include <haproxy/protocol.h>
54#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020055#include <haproxy/regex.h>
56#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020057#include <haproxy/server.h>
58#include <haproxy/ssl_sock.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020059#include <haproxy/task.h>
60#include <haproxy/tcpcheck.h>
Willy Tarreau9310f482021-10-06 16:18:40 +020061#include <haproxy/ticks.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020062#include <haproxy/tools.h>
Christopher Faulet147b8c92021-04-10 09:00:38 +020063#include <haproxy/trace.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020064#include <haproxy/vars.h>
65
66
Christopher Faulet147b8c92021-04-10 09:00:38 +020067#define TRACE_SOURCE &trace_check
68
Willy Tarreau51cd5952020-06-05 12:25:38 +020069/* Global tree to share all tcp-checks */
70struct eb_root shared_tcpchecks = EB_ROOT;
71
72
73DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
74
75/**************************************************************************/
76/*************** Init/deinit tcp-check rules and ruleset ******************/
77/**************************************************************************/
78/* Releases memory allocated for a log-format string */
79static void free_tcpcheck_fmt(struct list *fmt)
80{
81 struct logformat_node *lf, *lfb;
82
83 list_for_each_entry_safe(lf, lfb, fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +020084 LIST_DELETE(&lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +020085 release_sample_expr(lf->expr);
86 free(lf->arg);
87 free(lf);
88 }
89}
90
91/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
92void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
93{
94 if (!hdr)
95 return;
96
97 free_tcpcheck_fmt(&hdr->value);
98 istfree(&hdr->name);
99 free(hdr);
100}
101
102/* Releases memory allocated for an HTTP header list used in a tcp-check send
103 * rule
104 */
105static void free_tcpcheck_http_hdrs(struct list *hdrs)
106{
107 struct tcpcheck_http_hdr *hdr, *bhdr;
108
109 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200110 LIST_DELETE(&hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200111 free_tcpcheck_http_hdr(hdr);
112 }
113}
114
115/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
116 * tcp-check was allocated using a memory pool (it is used to instantiate email
117 * alerts).
118 */
119void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
120{
121 if (!rule)
122 return;
123
124 free(rule->comment);
125 switch (rule->action) {
126 case TCPCHK_ACT_SEND:
127 switch (rule->send.type) {
128 case TCPCHK_SEND_STRING:
129 case TCPCHK_SEND_BINARY:
130 istfree(&rule->send.data);
131 break;
132 case TCPCHK_SEND_STRING_LF:
133 case TCPCHK_SEND_BINARY_LF:
134 free_tcpcheck_fmt(&rule->send.fmt);
135 break;
136 case TCPCHK_SEND_HTTP:
137 free(rule->send.http.meth.str.area);
138 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
139 istfree(&rule->send.http.uri);
140 else
141 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
142 istfree(&rule->send.http.vsn);
143 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
144 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
145 istfree(&rule->send.http.body);
146 else
147 free_tcpcheck_fmt(&rule->send.http.body_fmt);
148 break;
149 case TCPCHK_SEND_UNDEF:
150 break;
151 }
152 break;
153 case TCPCHK_ACT_EXPECT:
154 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
155 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
156 release_sample_expr(rule->expect.status_expr);
157 switch (rule->expect.type) {
158 case TCPCHK_EXPECT_HTTP_STATUS:
159 free(rule->expect.codes.codes);
160 break;
161 case TCPCHK_EXPECT_STRING:
162 case TCPCHK_EXPECT_BINARY:
163 case TCPCHK_EXPECT_HTTP_BODY:
164 istfree(&rule->expect.data);
165 break;
166 case TCPCHK_EXPECT_STRING_REGEX:
167 case TCPCHK_EXPECT_BINARY_REGEX:
168 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
169 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
170 regex_free(rule->expect.regex);
171 break;
172 case TCPCHK_EXPECT_STRING_LF:
173 case TCPCHK_EXPECT_BINARY_LF:
174 case TCPCHK_EXPECT_HTTP_BODY_LF:
175 free_tcpcheck_fmt(&rule->expect.fmt);
176 break;
177 case TCPCHK_EXPECT_HTTP_HEADER:
178 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
179 regex_free(rule->expect.hdr.name_re);
180 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
181 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
182 else
183 istfree(&rule->expect.hdr.name);
184
185 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
186 regex_free(rule->expect.hdr.value_re);
187 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
188 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
189 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
190 istfree(&rule->expect.hdr.value);
191 break;
192 case TCPCHK_EXPECT_CUSTOM:
193 case TCPCHK_EXPECT_UNDEF:
194 break;
195 }
196 break;
197 case TCPCHK_ACT_CONNECT:
198 free(rule->connect.sni);
199 free(rule->connect.alpn);
200 release_sample_expr(rule->connect.port_expr);
201 break;
202 case TCPCHK_ACT_COMMENT:
203 break;
204 case TCPCHK_ACT_ACTION_KW:
205 free(rule->action_kw.rule);
206 break;
207 }
208
209 if (in_pool)
210 pool_free(pool_head_tcpcheck_rule, rule);
211 else
212 free(rule);
213}
214
215/* Creates a tcp-check variable used in preset variables before executing a
216 * tcp-check ruleset.
217 */
218struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
219{
220 struct tcpcheck_var *var = NULL;
221
222 var = calloc(1, sizeof(*var));
223 if (var == NULL)
224 return NULL;
225
226 var->name = istdup(name);
227 if (!isttest(var->name)) {
228 free(var);
229 return NULL;
230 }
231
232 LIST_INIT(&var->list);
233 return var;
234}
235
236/* Releases memory allocated for a preset tcp-check variable */
237void free_tcpcheck_var(struct tcpcheck_var *var)
238{
239 if (!var)
240 return;
241
242 istfree(&var->name);
243 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
244 free(var->data.u.str.area);
245 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
246 free(var->data.u.meth.str.area);
247 free(var);
248}
249
250/* Releases a list of preset tcp-check variables */
251void free_tcpcheck_vars(struct list *vars)
252{
253 struct tcpcheck_var *var, *back;
254
255 list_for_each_entry_safe(var, back, vars, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200256 LIST_DELETE(&var->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200257 free_tcpcheck_var(var);
258 }
259}
260
261/* Duplicate a list of preset tcp-check variables */
Willy Tarreau09f2e772021-02-12 08:42:30 +0100262int dup_tcpcheck_vars(struct list *dst, const struct list *src)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200263{
Willy Tarreau09f2e772021-02-12 08:42:30 +0100264 const struct tcpcheck_var *var;
265 struct tcpcheck_var *new = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200266
267 list_for_each_entry(var, src, list) {
268 new = create_tcpcheck_var(var->name);
269 if (!new)
270 goto error;
271 new->data.type = var->data.type;
272 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
273 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
274 goto error;
275 if (var->data.type == SMP_T_STR)
276 new->data.u.str.area[new->data.u.str.data] = 0;
277 }
278 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
279 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
280 goto error;
281 new->data.u.str.area[new->data.u.str.data] = 0;
282 new->data.u.meth.meth = var->data.u.meth.meth;
283 }
284 else
285 new->data.u = var->data.u;
Willy Tarreau2b718102021-04-21 07:32:39 +0200286 LIST_APPEND(dst, &new->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200287 }
288 return 1;
289
290 error:
291 free(new);
292 return 0;
293}
294
295/* Looks for a shared tcp-check ruleset given its name. */
296struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
297{
298 struct tcpcheck_ruleset *rs;
299 struct ebpt_node *node;
300
301 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
302 if (node) {
303 rs = container_of(node, typeof(*rs), node);
304 return rs;
305 }
306 return NULL;
307}
308
309/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks
310 * tree.
311 */
312struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
313{
314 struct tcpcheck_ruleset *rs;
315
316 rs = calloc(1, sizeof(*rs));
317 if (rs == NULL)
318 return NULL;
319
320 rs->node.key = strdup(name);
321 if (rs->node.key == NULL) {
322 free(rs);
323 return NULL;
324 }
325
326 LIST_INIT(&rs->rules);
327 ebis_insert(&shared_tcpchecks, &rs->node);
328 return rs;
329}
330
331/* Releases memory allocated by a tcp-check ruleset. */
332void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
333{
334 struct tcpcheck_rule *r, *rb;
335
336 if (!rs)
337 return;
338
339 ebpt_delete(&rs->node);
340 free(rs->node.key);
341 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200342 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200343 free_tcpcheck(r, 0);
344 }
345 free(rs);
346}
347
348
349/**************************************************************************/
350/**************** Everything about tcp-checks execution *******************/
351/**************************************************************************/
352/* Returns the id of a step in a tcp-check ruleset */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200353int tcpcheck_get_step_id(const struct check *check, const struct tcpcheck_rule *rule)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200354{
355 if (!rule)
356 rule = check->current_step;
357
358 /* no last started step => first step */
359 if (!rule)
360 return 1;
361
362 /* last step is the first implicit connect */
363 if (rule->index == 0 &&
364 rule->action == TCPCHK_ACT_CONNECT &&
365 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
366 return 0;
367
368 return rule->index + 1;
369}
370
371/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
372 * NULL if none was found.
373 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200374struct tcpcheck_rule *get_first_tcpcheck_rule(const struct tcpcheck_rules *rules)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200375{
376 struct tcpcheck_rule *r;
377
378 list_for_each_entry(r, rules->list, list) {
379 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
380 return r;
381 }
382 return NULL;
383}
384
385/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
386 * NULL if none was found.
387 */
388static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
389{
390 struct tcpcheck_rule *r;
391
392 list_for_each_entry_rev(r, rules->list, list) {
393 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
394 return r;
395 }
396 return NULL;
397}
398
399/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
400 * <start> or NULL if non was found. If <start> is NULL, it relies on
401 * get_first_tcpcheck_rule().
402 */
403static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
404{
405 struct tcpcheck_rule *r;
406
407 if (!start)
408 return get_first_tcpcheck_rule(rules);
409
410 r = LIST_NEXT(&start->list, typeof(r), list);
411 list_for_each_entry_from(r, rules->list, list) {
412 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
413 return r;
414 }
415 return NULL;
416}
417
418
419/* Creates info message when a tcp-check healthcheck fails on an expect rule */
420static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
421 int match, struct ist info)
422{
423 struct sample *smp;
424
425 /* Follows these step to produce the info message:
426 * 1. if info field is already provided, copy it
427 * 2. if the expect rule provides an onerror log-format string,
428 * use it to produce the message
429 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
430 * 4. Otherwise produce the generic tcp-check info message
431 */
432 if (istlen(info)) {
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100433 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200434 goto comment;
435 }
436 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
437 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
438 goto comment;
439 }
440
441 if (check->type == PR_O2_TCPCHK_CHK &&
442 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
443 goto comment;
444
445 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
446 switch (rule->expect.type) {
447 case TCPCHK_EXPECT_HTTP_STATUS:
448 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
449 break;
450 case TCPCHK_EXPECT_STRING:
451 case TCPCHK_EXPECT_HTTP_BODY:
452 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
453 tcpcheck_get_step_id(check, rule));
454 break;
455 case TCPCHK_EXPECT_BINARY:
456 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
457 break;
458 case TCPCHK_EXPECT_STRING_REGEX:
459 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
460 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
461 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
462 break;
463 case TCPCHK_EXPECT_BINARY_REGEX:
464 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
465 break;
466 case TCPCHK_EXPECT_STRING_LF:
467 case TCPCHK_EXPECT_HTTP_BODY_LF:
468 chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule));
469 break;
470 case TCPCHK_EXPECT_BINARY_LF:
471 chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule));
472 break;
473 case TCPCHK_EXPECT_CUSTOM:
474 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
475 break;
476 case TCPCHK_EXPECT_HTTP_HEADER:
477 chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
478 case TCPCHK_EXPECT_UNDEF:
479 /* Should never happen. */
480 return;
481 }
482
483 comment:
484 /* If the failing expect rule provides a comment, it is concatenated to
485 * the info message.
486 */
487 if (rule->comment) {
488 chunk_strcat(msg, " comment: ");
489 chunk_strcat(msg, rule->comment);
490 }
491
492 /* Finally, the check status code is set if the failing expect rule
493 * defines a status expression.
494 */
495 if (rule->expect.status_expr) {
496 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
497 rule->expect.status_expr, SMP_T_STR);
498
499 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
500 sample_casts[smp->data.type][SMP_T_SINT](smp))
501 check->code = smp->data.u.sint;
502 }
503
504 *(b_tail(msg)) = '\0';
505}
506
507/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
508static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
509 struct ist info)
510{
511 struct sample *smp;
512
513 /* Follows these step to produce the info message:
514 * 1. if info field is already provided, copy it
515 * 2. if the expect rule provides an onsucces log-format string,
516 * use it to produce the message
517 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
518 * 4. Otherwise produce the generic tcp-check info message
519 */
520 if (istlen(info))
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100521 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200522 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
523 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
524 &rule->expect.onsuccess_fmt);
525 else if (check->type == PR_O2_TCPCHK_CHK &&
526 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
527 chunk_strcat(msg, "(tcp-check)");
528
529 /* Finally, the check status code is set if the expect rule defines a
530 * status expression.
531 */
532 if (rule->expect.status_expr) {
533 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
534 rule->expect.status_expr, SMP_T_STR);
535
536 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
537 sample_casts[smp->data.type][SMP_T_SINT](smp))
538 check->code = smp->data.u.sint;
539 }
540
541 *(b_tail(msg)) = '\0';
542}
543
544/* Internal functions to parse and validate a MySQL packet in the context of an
545 * expect rule. It start to parse the input buffer at the offset <offset>. If
546 * <last_read> is set, no more data are expected.
547 */
548static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
549 unsigned int offset, int last_read)
550{
551 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
552 enum healthcheck_status status;
553 struct buffer *msg = NULL;
554 struct ist desc = IST_NULL;
555 unsigned int err = 0, plen = 0;
556
557
Christopher Faulet147b8c92021-04-10 09:00:38 +0200558 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
559
Willy Tarreau51cd5952020-06-05 12:25:38 +0200560 /* 3 Bytes for the packet length and 1 byte for the sequence id */
561 if (b_data(&check->bi) < offset+4) {
562 if (!last_read)
563 goto wait_more_data;
564
565 /* invalid length or truncated response */
566 status = HCHK_STATUS_L7RSP;
567 goto error;
568 }
569
570 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
571 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
572 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
573
574 if (b_data(&check->bi) < offset+plen+4) {
575 if (!last_read)
576 goto wait_more_data;
577
578 /* invalid length or truncated response */
579 status = HCHK_STATUS_L7RSP;
580 goto error;
581 }
582
583 if (*b_peek(&check->bi, offset+4) == '\xff') {
584 /* MySQL Error packet always begin with field_count = 0xff */
585 status = HCHK_STATUS_L7STS;
586 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
587 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
588 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
589 goto error;
590 }
591
592 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
593 /* Not the last rule, continue */
594 goto out;
595 }
596
597 /* We set the MySQL Version in description for information purpose
598 * FIXME : it can be cool to use MySQL Version for other purpose,
599 * like mark as down old MySQL server.
600 */
601 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
602 set_server_check_status(check, status, b_peek(&check->bi, 5));
603
604 out:
605 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200606 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200607 return ret;
608
609 error:
610 ret = TCPCHK_EVAL_STOP;
611 check->code = err;
612 msg = alloc_trash_chunk();
613 if (msg)
614 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
615 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
616 goto out;
617
618 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200619 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200620 ret = TCPCHK_EVAL_WAIT;
621 goto out;
622}
623
624/* Custom tcp-check expect function to parse and validate the MySQL initial
625 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
626 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
627 * error occurred.
628 */
629enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
630{
631 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
632}
633
634/* Custom tcp-check expect function to parse and validate the MySQL OK packet
635 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
636 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
637 * an error occurred.
638 */
639enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
640{
641 unsigned int hslen = 0;
642
643 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
644 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
645 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
646
647 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
648}
649
650/* Custom tcp-check expect function to parse and validate the LDAP bind response
651 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
652 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
653 * error occurred.
654 */
655enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
656{
657 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
658 enum healthcheck_status status;
659 struct buffer *msg = NULL;
660 struct ist desc = IST_NULL;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200661 char *ptr;
662 unsigned short nbytes = 0;
663 size_t msglen = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200664
Christopher Faulet147b8c92021-04-10 09:00:38 +0200665 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
666
Willy Tarreau51cd5952020-06-05 12:25:38 +0200667 /* Check if the server speaks LDAP (ASN.1/BER)
668 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
669 * http://tools.ietf.org/html/rfc4511
670 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200671 ptr = b_head(&check->bi) + 1;
672
Willy Tarreau51cd5952020-06-05 12:25:38 +0200673 /* size of LDAPMessage */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200674 if (*ptr & 0x80) {
675 /* For message size encoded on several bytes, we only handle
676 * size encoded on 2 or 4 bytes. There is no reason to make this
677 * part to complex because only Active Directory is known to
678 * encode BindReponse length on 4 bytes.
679 */
680 nbytes = (*ptr & 0x7f);
681 if (b_data(&check->bi) < 1 + nbytes)
682 goto too_short;
683 switch (nbytes) {
684 case 4: msglen = read_n32(ptr+1); break;
685 case 2: msglen = read_n16(ptr+1); break;
686 default:
687 status = HCHK_STATUS_L7RSP;
688 desc = ist("Not LDAPv3 protocol");
689 goto error;
690 }
691 }
692 else
693 msglen = *ptr;
694 ptr += 1 + nbytes;
695
696 if (b_data(&check->bi) < 2 + nbytes + msglen)
697 goto too_short;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200698
699 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
700 * messageID: 0x02 0x01 0x01: INTEGER 1
701 * protocolOp: 0x61: bindResponse
702 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200703 if (memcmp(ptr, "\x02\x01\x01\x61", 4) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200704 status = HCHK_STATUS_L7RSP;
705 desc = ist("Not LDAPv3 protocol");
706 goto error;
707 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200708 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200709
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200710 /* skip size of bindResponse */
711 nbytes = 0;
712 if (*ptr & 0x80)
713 nbytes = (*ptr & 0x7f);
714 ptr += 1 + nbytes;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200715
716 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
717 * ldapResult: 0x0a 0x01: ENUMERATION
718 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200719 if (memcmp(ptr, "\x0a\x01", 2) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200720 status = HCHK_STATUS_L7RSP;
721 desc = ist("Not LDAPv3 protocol");
722 goto error;
723 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200724 ptr += 2;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200725
726 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
727 * resultCode
728 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200729 check->code = *ptr;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200730 if (check->code) {
731 status = HCHK_STATUS_L7STS;
732 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
733 goto error;
734 }
735
736 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
737 set_server_check_status(check, status, "Success");
738
739 out:
740 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200741 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200742 return ret;
743
744 error:
745 ret = TCPCHK_EVAL_STOP;
746 msg = alloc_trash_chunk();
747 if (msg)
748 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
749 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
750 goto out;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200751
752 too_short:
753 if (!last_read)
754 goto wait_more_data;
755 /* invalid length or truncated response */
756 status = HCHK_STATUS_L7RSP;
757 goto error;
758
759 wait_more_data:
760 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
761 ret = TCPCHK_EVAL_WAIT;
762 goto out;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200763}
764
765/* Custom tcp-check expect function to parse and validate the SPOP hello agent
766 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
767 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
768 */
769enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
770{
771 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
772 enum healthcheck_status status;
773 struct buffer *msg = NULL;
774 struct ist desc = IST_NULL;
775 unsigned int framesz;
776
Christopher Faulet147b8c92021-04-10 09:00:38 +0200777 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200778
779 memcpy(&framesz, b_head(&check->bi), 4);
780 framesz = ntohl(framesz);
781
782 if (!last_read && b_data(&check->bi) < (4+framesz))
783 goto wait_more_data;
784
785 memset(b_orig(&trash), 0, b_size(&trash));
786 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
787 status = HCHK_STATUS_L7RSP;
788 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
789 goto error;
790 }
791
792 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
793 set_server_check_status(check, status, "SPOA server is ok");
794
795 out:
796 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200797 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200798 return ret;
799
800 error:
801 ret = TCPCHK_EVAL_STOP;
802 msg = alloc_trash_chunk();
803 if (msg)
804 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
805 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
806 goto out;
807
808 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200809 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200810 ret = TCPCHK_EVAL_WAIT;
811 goto out;
812}
813
814/* Custom tcp-check expect function to parse and validate the agent-check
815 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
816 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
817 */
818enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
819{
820 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
821 enum healthcheck_status status = HCHK_STATUS_CHECKED;
822 const char *hs = NULL; /* health status */
823 const char *as = NULL; /* admin status */
824 const char *ps = NULL; /* performance status */
825 const char *cs = NULL; /* maxconn */
826 const char *err = NULL; /* first error to report */
827 const char *wrn = NULL; /* first warning to report */
828 char *cmd, *p;
829
Christopher Faulet147b8c92021-04-10 09:00:38 +0200830 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
831
Willy Tarreau51cd5952020-06-05 12:25:38 +0200832 /* We're getting an agent check response. The agent could
833 * have been disabled in the mean time with a long check
834 * still pending. It is important that we ignore the whole
835 * response.
836 */
837 if (!(check->state & CHK_ST_ENABLED))
838 goto out;
839
840 /* The agent supports strings made of a single line ended by the
841 * first CR ('\r') or LF ('\n'). This line is composed of words
842 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
843 * line may optionally contained a description of a state change
844 * after a sharp ('#'), which is only considered if a health state
845 * is announced.
846 *
847 * Words may be composed of :
848 * - a numeric weight suffixed by the percent character ('%').
849 * - a health status among "up", "down", "stopped", and "fail".
850 * - an admin status among "ready", "drain", "maint".
851 *
852 * These words may appear in any order. If multiple words of the
853 * same category appear, the last one wins.
854 */
855
856 p = b_head(&check->bi);
857 while (*p && *p != '\n' && *p != '\r')
858 p++;
859
860 if (!*p) {
861 if (!last_read)
862 goto wait_more_data;
863
864 /* at least inform the admin that the agent is mis-behaving */
865 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
866 goto out;
867 }
868
869 *p = 0;
870 cmd = b_head(&check->bi);
871
872 while (*cmd) {
873 /* look for next word */
874 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
875 cmd++;
876 continue;
877 }
878
879 if (*cmd == '#') {
880 /* this is the beginning of a health status description,
881 * skip the sharp and blanks.
882 */
883 cmd++;
884 while (*cmd == '\t' || *cmd == ' ')
885 cmd++;
886 break;
887 }
888
889 /* find the end of the word so that we have a null-terminated
890 * word between <cmd> and <p>.
891 */
892 p = cmd + 1;
893 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
894 p++;
895 if (*p)
896 *p++ = 0;
897
898 /* first, health statuses */
899 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100900 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200901 status = HCHK_STATUS_L7OKD;
902 hs = cmd;
903 }
904 else if (strcasecmp(cmd, "down") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100905 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200906 status = HCHK_STATUS_L7STS;
907 hs = cmd;
908 }
909 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100910 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200911 status = HCHK_STATUS_L7STS;
912 hs = cmd;
913 }
914 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100915 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200916 status = HCHK_STATUS_L7STS;
917 hs = cmd;
918 }
919 /* admin statuses */
920 else if (strcasecmp(cmd, "ready") == 0) {
921 as = cmd;
922 }
923 else if (strcasecmp(cmd, "drain") == 0) {
924 as = cmd;
925 }
926 else if (strcasecmp(cmd, "maint") == 0) {
927 as = cmd;
928 }
929 /* try to parse a weight here and keep the last one */
930 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
931 ps = cmd;
932 }
933 /* try to parse a maxconn here */
934 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
935 cs = cmd;
936 }
937 else {
938 /* keep a copy of the first error */
939 if (!err)
940 err = cmd;
941 }
942 /* skip to next word */
943 cmd = p;
944 }
945 /* here, cmd points either to \0 or to the beginning of a
946 * description. Skip possible leading spaces.
947 */
948 while (*cmd == ' ' || *cmd == '\n')
949 cmd++;
950
951 /* First, update the admin status so that we avoid sending other
952 * possibly useless warnings and can also update the health if
953 * present after going back up.
954 */
955 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200956 if (strcasecmp(as, "drain") == 0) {
957 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200958 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200959 }
960 else if (strcasecmp(as, "maint") == 0) {
961 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200962 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200963 }
964 else {
965 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200966 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200967 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200968 }
969
970 /* now change weights */
971 if (ps) {
972 const char *msg;
973
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500974 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200975 msg = server_parse_weight_change_request(check->server, ps);
976 if (!wrn || !*wrn)
977 wrn = msg;
978 }
979
980 if (cs) {
981 const char *msg;
982
983 cs += strlen("maxconn:");
984
Christopher Faulet147b8c92021-04-10 09:00:38 +0200985 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury Denoyelle02742862021-06-18 11:11:36 +0200986 /* This is safe to call server_parse_maxconn_change_request
987 * because the server lock is held during the check.
988 */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200989 msg = server_parse_maxconn_change_request(check->server, cs);
990 if (!wrn || !*wrn)
991 wrn = msg;
992 }
993
994 /* and finally health status */
995 if (hs) {
996 /* We'll report some of the warnings and errors we have
997 * here. Down reports are critical, we leave them untouched.
998 * Lack of report, or report of 'UP' leaves the room for
999 * ERR first, then WARN.
1000 */
1001 const char *msg = cmd;
1002 struct buffer *t;
1003
1004 if (!*msg || status == HCHK_STATUS_L7OKD) {
1005 if (err && *err)
1006 msg = err;
1007 else if (wrn && *wrn)
1008 msg = wrn;
1009 }
1010
1011 t = get_trash_chunk();
1012 chunk_printf(t, "via agent : %s%s%s%s",
1013 hs, *msg ? " (" : "",
1014 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001015 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001016 set_server_check_status(check, status, t->area);
1017 }
1018 else if (err && *err) {
1019 /* No status change but we'd like to report something odd.
1020 * Just report the current state and copy the message.
1021 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001022 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001023 chunk_printf(&trash, "agent reports an error : %s", err);
1024 set_server_check_status(check, status/*check->status*/, trash.area);
1025 }
1026 else if (wrn && *wrn) {
1027 /* No status change but we'd like to report something odd.
1028 * Just report the current state and copy the message.
1029 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001030 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001031 chunk_printf(&trash, "agent warns : %s", wrn);
1032 set_server_check_status(check, status/*check->status*/, trash.area);
1033 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001034 else {
1035 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001036 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001037 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001038
1039 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001040 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001041 return ret;
1042
1043 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001044 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001045 ret = TCPCHK_EVAL_WAIT;
1046 goto out;
1047}
1048
1049/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1050 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1051 * TCPCHK_EVAL_STOP if an error occurred.
1052 */
1053enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1054{
1055 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1056 struct tcpcheck_connect *connect = &rule->connect;
1057 struct proxy *proxy = check->proxy;
1058 struct server *s = check->server;
1059 struct task *t = check->task;
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001060 struct conn_stream *cs = check->cs;
1061 struct connection *conn = cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001062 struct protocol *proto;
1063 struct xprt_ops *xprt;
1064 struct tcpcheck_rule *next;
1065 int status, port;
1066
Christopher Faulet147b8c92021-04-10 09:00:38 +02001067 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1068
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001069 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1070
1071 /* current connection already created, check if it is established or not */
1072 if (conn) {
1073 if (conn->flags & CO_FL_WAIT_XPRT) {
1074 /* We are still waiting for the connection establishment */
1075 if (next && next->action == TCPCHK_ACT_SEND) {
1076 if (!(check->wait_list.events & SUB_RETRY_SEND))
1077 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1078 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001079 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001080 }
1081 else
1082 ret = tcpcheck_eval_recv(check, rule);
1083 }
1084 goto out;
1085 }
1086
1087 /* Note: here check->cs = cs = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001088
Christopher Fauletb381a502020-11-25 13:47:00 +01001089 /* Always release input and output buffer when a new connect is evaluated */
1090 check_release_buf(check, &check->bi);
1091 check_release_buf(check, &check->bo);
1092
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001093 /* No connection, prepare a new one */
Christopher Fauletdd2d0d82021-12-20 09:34:32 +01001094 conn = conn_new((s ? &s->obj_type : &proxy->obj_type));
1095 if (conn)
1096 cs = cs_new(&conn->obj_type);
1097 if (!conn || !cs) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001098 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1099 tcpcheck_get_step_id(check, rule));
1100 if (rule->comment)
1101 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1102 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1103 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001104 TRACE_ERROR("conn-stream allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Fauletdd2d0d82021-12-20 09:34:32 +01001105 if (conn)
1106 conn_free(conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001107 goto out;
1108 }
1109
Willy Tarreau51cd5952020-06-05 12:25:38 +02001110 tasklet_set_tid(check->wait_list.tasklet, tid);
1111
1112 check->cs = cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001113 conn_set_owner(conn, check->sess, NULL);
1114
1115 /* Maybe there were an older connection we were waiting on */
1116 check->wait_list.events = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001117
1118 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001119 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001120 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001121 status = SF_ERR_RESOURCE;
1122 goto fail_check;
1123 }
1124
1125 /* connect to the connect rule addr if specified, otherwise the check
1126 * addr if specified on the server. otherwise, use the server addr (it
1127 * MUST exist at this step).
1128 */
1129 *conn->dst = (is_addr(&connect->addr)
1130 ? connect->addr
1131 : (is_addr(&check->addr) ? check->addr : s->addr));
Willy Tarreau14e7f292021-10-27 17:41:07 +02001132 proto = protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001133
1134 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001135 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001136 port = connect->port;
1137 if (!port && connect->port_expr) {
1138 struct sample *smp;
1139
1140 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1141 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1142 connect->port_expr, SMP_T_SINT);
1143 if (smp)
1144 port = smp->data.u.sint;
1145 }
1146 if (!port && is_inet_addr(&connect->addr))
1147 port = get_host_port(&connect->addr);
1148 if (!port && check->port)
1149 port = check->port;
1150 if (!port && is_inet_addr(&check->addr))
1151 port = get_host_port(&check->addr);
1152 if (!port) {
1153 /* The server MUST exist here */
1154 port = s->svc_port;
1155 }
1156 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001157 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001158
1159 xprt = ((connect->options & TCPCHK_OPT_SSL)
1160 ? xprt_get(XPRT_SSL)
1161 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1162
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001163 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001164 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001165 status = SF_ERR_RESOURCE;
1166 goto fail_check;
1167 }
1168
Willy Tarreau51cd5952020-06-05 12:25:38 +02001169 cs_attach(cs, check, &check_conn_cb);
1170
Christopher Fauletf7177272020-10-02 13:41:55 +02001171 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1172 conn->send_proxy_ofs = 1;
1173 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001174 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001175 }
1176 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1177 conn->send_proxy_ofs = 1;
1178 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001179 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001180 }
1181
1182 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1183 conn->send_proxy_ofs = 1;
1184 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001185 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001186 }
1187 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1188 conn->send_proxy_ofs = 1;
1189 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001190 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001191 }
1192
Willy Tarreau51cd5952020-06-05 12:25:38 +02001193 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001194 if (proto && proto->connect) {
1195 int flags = 0;
1196
1197 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1198 flags |= CONNECT_HAS_DATA;
1199 if (!next || next->action != TCPCHK_ACT_EXPECT)
1200 flags |= CONNECT_DELACK_ALWAYS;
1201 status = proto->connect(conn, flags);
1202 }
1203
1204 if (status != SF_ERR_NONE)
1205 goto fail_check;
1206
Christopher Faulet21ddc742020-07-01 15:26:14 +02001207 conn_set_private(conn);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001208 conn->ctx = cs;
1209
Willy Tarreau51cd5952020-06-05 12:25:38 +02001210#ifdef USE_OPENSSL
1211 if (connect->sni)
1212 ssl_sock_set_servername(conn, connect->sni);
1213 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1214 ssl_sock_set_servername(conn, s->check.sni);
1215
1216 if (connect->alpn)
1217 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1218 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1219 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1220#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001221
1222 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER)) {
1223 /* Some servers don't like reset on close */
Christopher Faulet897d6122021-12-17 17:28:35 +01001224 HA_ATOMIC_AND(&fdtab[conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001225 }
1226
1227 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1228 if (xprt_add_hs(conn) < 0)
1229 status = SF_ERR_RESOURCE;
1230 }
1231
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001232 if (conn_xprt_start(conn) < 0) {
1233 status = SF_ERR_RESOURCE;
1234 goto fail_check;
1235 }
1236
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001237 /* The mux may be initialized now if there isn't server attached to the
1238 * check (email alerts) or if there is a mux proto specified or if there
1239 * is no alpn.
1240 */
1241 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1242 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1243 const struct mux_ops *mux_ops;
1244
Christopher Faulet147b8c92021-04-10 09:00:38 +02001245 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001246 if (connect->mux_proto)
1247 mux_ops = connect->mux_proto->mux;
1248 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1249 mux_ops = check->mux_proto->mux;
1250 else {
1251 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1252 ? PROTO_MODE_HTTP
1253 : PROTO_MODE_TCP);
1254
1255 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1256 }
1257 if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001258 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001259 status = SF_ERR_INTERNAL;
1260 goto fail_check;
1261 }
1262 }
1263
Willy Tarreau51cd5952020-06-05 12:25:38 +02001264 fail_check:
1265 /* It can return one of :
1266 * - SF_ERR_NONE if everything's OK
1267 * - SF_ERR_SRVTO if there are no more servers
1268 * - SF_ERR_SRVCL if the connection was refused by the server
1269 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1270 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1271 * - SF_ERR_INTERNAL for any other purely internal errors
1272 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1273 * Note that we try to prevent the network stack from sending the ACK during the
1274 * connect() when a pure TCP check is used (without PROXY protocol).
1275 */
1276 switch (status) {
1277 case SF_ERR_NONE:
1278 /* we allow up to min(inter, timeout.connect) for a connection
1279 * to establish but only when timeout.check is set as it may be
1280 * to short for a full check otherwise
1281 */
1282 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1283
1284 if (proxy->timeout.check && proxy->timeout.connect) {
1285 int t_con = tick_add(now_ms, proxy->timeout.connect);
1286 t->expire = tick_first(t->expire, t_con);
1287 }
1288 break;
1289 case SF_ERR_SRVTO: /* ETIMEDOUT */
1290 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1291 case SF_ERR_PRXCOND:
1292 case SF_ERR_RESOURCE:
1293 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001294 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check, 0, 0, (size_t[]){status});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001295 chk_report_conn_err(check, errno, 0);
1296 ret = TCPCHK_EVAL_STOP;
1297 goto out;
1298 }
1299
1300 /* don't do anything until the connection is established */
1301 if (conn->flags & CO_FL_WAIT_XPRT) {
1302 if (conn->mux) {
1303 if (next && next->action == TCPCHK_ACT_SEND)
1304 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
1305 else
1306 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
1307 }
1308 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001309 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001310 goto out;
1311 }
1312
1313 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001314 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001315 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001316 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001317 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001318
1319 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1320 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1321
Christopher Faulet147b8c92021-04-10 09:00:38 +02001322 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001323 return ret;
1324}
1325
1326/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1327 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1328 * TCPCHK_EVAL_STOP if an error occurred.
1329 */
1330enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1331{
1332 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1333 struct tcpcheck_send *send = &rule->send;
1334 struct conn_stream *cs = check->cs;
1335 struct connection *conn = cs_conn(cs);
1336 struct buffer *tmp = NULL;
1337 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001338 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001339
Christopher Faulet147b8c92021-04-10 09:00:38 +02001340 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1341
Christopher Fauletb381a502020-11-25 13:47:00 +01001342 if (check->state & CHK_ST_OUT_ALLOC) {
1343 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001344 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001345 goto out;
1346 }
1347
1348 if (!check_get_buf(check, &check->bo)) {
1349 check->state |= CHK_ST_OUT_ALLOC;
1350 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001351 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001352 goto out;
1353 }
1354
Christopher Faulet39066c22020-11-25 13:34:51 +01001355 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001356 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001357 TRACE_DEVEL("Data still pending, try to send it now", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Christopher Faulet39066c22020-11-25 13:34:51 +01001358 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001359 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001360
Christopher Fauletb381a502020-11-25 13:47:00 +01001361 /* Always release input buffer when a new send is evaluated */
1362 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001363
1364 switch (send->type) {
1365 case TCPCHK_SEND_STRING:
1366 case TCPCHK_SEND_BINARY:
1367 if (istlen(send->data) >= b_size(&check->bo)) {
1368 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1369 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1370 tcpcheck_get_step_id(check, rule));
1371 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1372 ret = TCPCHK_EVAL_STOP;
1373 goto out;
1374 }
1375 b_putist(&check->bo, send->data);
1376 break;
1377 case TCPCHK_SEND_STRING_LF:
1378 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1379 if (!b_data(&check->bo))
1380 goto out;
1381 break;
1382 case TCPCHK_SEND_BINARY_LF: {
1383 int len = b_size(&check->bo);
1384
1385 tmp = alloc_trash_chunk();
1386 if (!tmp)
1387 goto error_lf;
1388 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1389 if (!b_data(tmp))
1390 goto out;
1391 tmp->area[tmp->data] = '\0';
1392 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1393 goto error_lf;
1394 check->bo.data = len;
1395 break;
1396 }
1397 case TCPCHK_SEND_HTTP: {
1398 struct htx_sl *sl;
1399 struct ist meth, uri, vsn, clen, body;
1400 unsigned int slflags = 0;
1401
1402 tmp = alloc_trash_chunk();
1403 if (!tmp)
1404 goto error_htx;
1405
1406 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1407 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1408 : http_known_methods[send->http.meth.meth]);
1409 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1410 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1411 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1412 }
1413 else
1414 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1415 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1416
1417 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1418 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1419 slflags |= HTX_SL_F_VER_11;
1420 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1421 if (!isttest(send->http.body))
1422 slflags |= HTX_SL_F_BODYLESS;
1423
1424 htx = htx_from_buf(&check->bo);
1425 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1426 if (!sl)
1427 goto error_htx;
1428 sl->info.req.meth = send->http.meth.meth;
1429 if (!http_update_host(htx, sl, uri))
1430 goto error_htx;
1431
1432 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1433 struct tcpcheck_http_hdr *hdr;
1434 struct ist hdr_value;
1435
1436 list_for_each_entry(hdr, &send->http.hdrs, list) {
1437 chunk_reset(tmp);
1438 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1439 if (!b_data(tmp))
1440 continue;
1441 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1442 if (!htx_add_header(htx, hdr->name, hdr_value))
1443 goto error_htx;
1444 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1445 if (!http_update_authority(htx, sl, hdr_value))
1446 goto error_htx;
1447 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001448 if (isteqi(hdr->name, ist("connection")))
1449 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001450 }
1451
1452 }
1453 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1454 chunk_reset(tmp);
1455 httpchk_build_status_header(check->server, tmp);
1456 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1457 goto error_htx;
1458 }
1459
1460
1461 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1462 chunk_reset(tmp);
1463 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1464 body = ist2(b_orig(tmp), b_data(tmp));
1465 }
1466 else
1467 body = send->http.body;
1468 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1469
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001470 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001471 !htx_add_header(htx, ist("Content-length"), clen))
1472 goto error_htx;
1473
1474
1475 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001476 (istlen(body) && !htx_add_data_atonce(htx, body)))
1477 goto error_htx;
1478
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001479 /* no more data are expected */
1480 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001481 htx_to_buf(htx, &check->bo);
1482 break;
1483 }
1484 case TCPCHK_SEND_UNDEF:
1485 /* Should never happen. */
1486 ret = TCPCHK_EVAL_STOP;
1487 goto out;
1488 };
1489
Christopher Faulet39066c22020-11-25 13:34:51 +01001490 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001491 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001492 if (conn->mux->snd_buf(cs, &check->bo,
1493 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
1494 if ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR)) {
1495 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001496 TRACE_DEVEL("connection error during send", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001497 goto out;
1498 }
1499 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001500 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Faulet897d6122021-12-17 17:28:35 +01001501 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001502 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001503 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001504 goto out;
1505 }
1506
1507 out:
1508 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001509 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1510 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001511
1512 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001513 return ret;
1514
1515 error_htx:
1516 if (htx) {
1517 htx_reset(htx);
1518 htx_to_buf(htx, &check->bo);
1519 }
1520 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1521 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001522 TRACE_ERROR("failed to build HTTP request", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001523 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1524 ret = TCPCHK_EVAL_STOP;
1525 goto out;
1526
1527 error_lf:
1528 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1529 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001530 TRACE_ERROR("failed to build log-format string", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001531 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1532 ret = TCPCHK_EVAL_STOP;
1533 goto out;
1534
1535}
1536
1537/* Try to receive data before evaluating a tcp-check expect rule. Returns
1538 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1539 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1540 * TCPCHK_EVAL_STOP if an error occurred.
1541 */
1542enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1543{
1544 struct conn_stream *cs = check->cs;
1545 struct connection *conn = cs_conn(cs);
1546 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1547 size_t max, read, cur_read = 0;
1548 int is_empty;
1549 int read_poll = MAX_READ_POLL_LOOPS;
1550
Christopher Faulet147b8c92021-04-10 09:00:38 +02001551 TRACE_ENTER(CHK_EV_RX_DATA, check);
1552
1553 if (check->wait_list.events & SUB_RETRY_RECV) {
1554 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001555 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001556 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001557
1558 if (cs->flags & CS_FL_EOS)
1559 goto end_recv;
1560
Christopher Faulet147b8c92021-04-10 09:00:38 +02001561 if (check->state & CHK_ST_IN_ALLOC) {
1562 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001563 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001564 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001565
1566 if (!check_get_buf(check, &check->bi)) {
1567 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001568 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001569 goto wait_more_data;
1570 }
1571
Willy Tarreau51cd5952020-06-05 12:25:38 +02001572 /* errors on the connection and the conn-stream were already checked */
1573
1574 /* prepare to detect if the mux needs more room */
1575 cs->flags &= ~CS_FL_WANT_ROOM;
1576
1577 while ((cs->flags & CS_FL_RCV_MORE) ||
1578 (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
1579 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1580 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1581 cur_read += read;
1582 if (!read ||
1583 (cs->flags & CS_FL_WANT_ROOM) ||
1584 (--read_poll <= 0) ||
1585 (read < max && read >= global.tune.recv_enough))
1586 break;
1587 }
1588
1589 end_recv:
1590 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
1591 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
1592 /* Report network errors only if we got no other data. Otherwise
1593 * we'll let the upper layers decide whether the response is OK
1594 * or not. It is very common that an RST sent by the server is
1595 * reported as an error just after the last data chunk.
1596 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001597 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001598 goto stop;
1599 }
1600 if (!cur_read) {
Christopher Fauletd16e7dd2021-10-20 13:53:38 +02001601 if (cs->flags & CS_FL_EOI) {
1602 /* If EOI is set, it means there is a response or an error */
1603 goto out;
1604 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001605 if (!(cs->flags & (CS_FL_WANT_ROOM|CS_FL_ERROR|CS_FL_EOS))) {
1606 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001607 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001608 goto wait_more_data;
1609 }
1610 if (is_empty) {
1611 int status;
1612
1613 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1614 tcpcheck_get_step_id(check, rule));
1615 if (rule->comment)
1616 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1617
Christopher Faulet147b8c92021-04-10 09:00:38 +02001618 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001619 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1620 set_server_check_status(check, status, trash.area);
1621 goto stop;
1622 }
1623 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001624 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001625
1626 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001627 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1628 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001629
1630 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001631 return ret;
1632
1633 stop:
1634 ret = TCPCHK_EVAL_STOP;
1635 goto out;
1636
1637 wait_more_data:
1638 ret = TCPCHK_EVAL_WAIT;
1639 goto out;
1640}
1641
1642/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1643 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1644 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1645 * error occurred.
1646 */
1647enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1648{
1649 struct htx *htx = htxbuf(&check->bi);
1650 struct htx_sl *sl;
1651 struct htx_blk *blk;
1652 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1653 struct tcpcheck_expect *expect = &rule->expect;
1654 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1655 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1656 struct ist desc = IST_NULL;
1657 int i, match, inverse;
1658
Christopher Faulet147b8c92021-04-10 09:00:38 +02001659 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1660
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001661 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001662
1663 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001664 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001665 status = HCHK_STATUS_L7RSP;
1666 goto error;
1667 }
1668
1669 if (htx_is_empty(htx)) {
1670 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001671 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001672 status = HCHK_STATUS_L7RSP;
1673 goto error;
1674 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001675 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001676 goto wait_more_data;
1677 }
1678
1679 sl = http_get_stline(htx);
1680 check->code = sl->info.res.status;
1681
1682 if (check->server &&
1683 (check->server->proxy->options & PR_O_DISABLE404) &&
1684 (check->server->next_state != SRV_ST_STOPPED) &&
1685 (check->code == 404)) {
1686 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001687 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001688 goto out;
1689 }
1690
1691 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1692 /* Make GCC happy ; initialize match to a failure state. */
1693 match = inverse;
1694 status = expect->err_status;
1695
1696 switch (expect->type) {
1697 case TCPCHK_EXPECT_HTTP_STATUS:
1698 match = 0;
1699 for (i = 0; i < expect->codes.num; i++) {
1700 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1701 sl->info.res.status <= expect->codes.codes[i][1]) {
1702 match = 1;
1703 break;
1704 }
1705 }
1706
1707 /* Set status and description in case of error */
1708 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1709 if (LIST_ISEMPTY(&expect->onerror_fmt))
1710 desc = htx_sl_res_reason(sl);
1711 break;
1712 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1713 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1714
1715 /* Set status and description in case of error */
1716 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1717 if (LIST_ISEMPTY(&expect->onerror_fmt))
1718 desc = htx_sl_res_reason(sl);
1719 break;
1720
1721 case TCPCHK_EXPECT_HTTP_HEADER: {
1722 struct http_hdr_ctx ctx;
1723 struct ist npat, vpat, value;
1724 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1725
1726 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1727 nbuf = alloc_trash_chunk();
1728 if (!nbuf) {
1729 status = HCHK_STATUS_L7RSP;
1730 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001731 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001732 goto error;
1733 }
1734 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1735 if (!b_data(nbuf)) {
1736 status = HCHK_STATUS_L7RSP;
1737 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001738 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001739 goto error;
1740 }
1741 npat = ist2(b_orig(nbuf), b_data(nbuf));
1742 }
1743 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1744 npat = expect->hdr.name;
1745
1746 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1747 vbuf = alloc_trash_chunk();
1748 if (!vbuf) {
1749 status = HCHK_STATUS_L7RSP;
1750 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001751 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001752 goto error;
1753 }
1754 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1755 if (!b_data(vbuf)) {
1756 status = HCHK_STATUS_L7RSP;
1757 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001758 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001759 goto error;
1760 }
1761 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1762 }
1763 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1764 vpat = expect->hdr.value;
1765
1766 match = 0;
1767 ctx.blk = NULL;
1768 while (1) {
1769 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1770 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1771 if (!http_find_str_header(htx, npat, &ctx, full))
1772 goto end_of_match;
1773 break;
1774 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1775 if (!http_find_pfx_header(htx, npat, &ctx, full))
1776 goto end_of_match;
1777 break;
1778 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1779 if (!http_find_sfx_header(htx, npat, &ctx, full))
1780 goto end_of_match;
1781 break;
1782 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1783 if (!http_find_sub_header(htx, npat, &ctx, full))
1784 goto end_of_match;
1785 break;
1786 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1787 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1788 goto end_of_match;
1789 break;
1790 default:
1791 /* should never happen */
1792 goto end_of_match;
1793 }
1794
1795 /* A header has matched the name pattern, let's test its
1796 * value now (always defined from there). If there is no
1797 * value pattern, it is a good match.
1798 */
1799
1800 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1801 match = 1;
1802 goto end_of_match;
1803 }
1804
1805 value = ctx.value;
1806 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1807 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1808 if (isteq(value, vpat)) {
1809 match = 1;
1810 goto end_of_match;
1811 }
1812 break;
1813 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1814 if (istlen(value) < istlen(vpat))
1815 break;
1816 value = ist2(istptr(value), istlen(vpat));
1817 if (isteq(value, vpat)) {
1818 match = 1;
1819 goto end_of_match;
1820 }
1821 break;
1822 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1823 if (istlen(value) < istlen(vpat))
1824 break;
Tim Duesterhus4c8f75f2021-11-06 15:14:44 +01001825 value = ist2(istend(value) - istlen(vpat), istlen(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001826 if (isteq(value, vpat)) {
1827 match = 1;
1828 goto end_of_match;
1829 }
1830 break;
1831 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1832 if (isttest(istist(value, vpat))) {
1833 match = 1;
1834 goto end_of_match;
1835 }
1836 break;
1837 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1838 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1839 match = 1;
1840 goto end_of_match;
1841 }
1842 break;
1843 }
1844 }
1845
1846 end_of_match:
1847 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1848 if (LIST_ISEMPTY(&expect->onerror_fmt))
1849 desc = htx_sl_res_reason(sl);
1850 break;
1851 }
1852
1853 case TCPCHK_EXPECT_HTTP_BODY:
1854 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1855 case TCPCHK_EXPECT_HTTP_BODY_LF:
1856 match = 0;
1857 chunk_reset(&trash);
1858 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1859 enum htx_blk_type type = htx_get_blk_type(blk);
1860
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001861 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001862 break;
1863 if (type == HTX_BLK_DATA) {
1864 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1865 break;
1866 }
1867 }
1868
1869 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001870 if (!last_read) {
1871 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001872 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001873 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001874 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1875 if (LIST_ISEMPTY(&expect->onerror_fmt))
1876 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001877 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001878 goto error;
1879 }
1880
1881 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1882 tmp = alloc_trash_chunk();
1883 if (!tmp) {
1884 status = HCHK_STATUS_L7RSP;
1885 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001886 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001887 goto error;
1888 }
1889 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1890 if (!b_data(tmp)) {
1891 status = HCHK_STATUS_L7RSP;
1892 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001893 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001894 goto error;
1895 }
1896 }
1897
1898 if (!last_read &&
1899 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1900 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1901 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1902 ret = TCPCHK_EVAL_WAIT;
1903 goto out;
1904 }
1905
1906 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1907 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1908 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1909 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1910 else
1911 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1912
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001913 /* Wait for more data on mismatch only if no minimum is defined (-1),
1914 * otherwise the absence of match is already conclusive.
1915 */
1916 if (!match && !last_read && (expect->min_recv == -1)) {
1917 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001918 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001919 goto out;
1920 }
1921
Willy Tarreau51cd5952020-06-05 12:25:38 +02001922 /* Set status and description in case of error */
1923 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1924 if (LIST_ISEMPTY(&expect->onerror_fmt))
1925 desc = (inverse
1926 ? ist("HTTP check matched unwanted content")
1927 : ist("HTTP content check did not match"));
1928 break;
1929
1930
1931 default:
1932 /* should never happen */
1933 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1934 goto error;
1935 }
1936
Christopher Faulet147b8c92021-04-10 09:00:38 +02001937 if (!(match ^ inverse)) {
1938 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001939 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001940 }
1941
1942 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001943
1944 out:
1945 free_trash_chunk(tmp);
1946 free_trash_chunk(nbuf);
1947 free_trash_chunk(vbuf);
1948 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001949 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001950 return ret;
1951
1952 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001953 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001954 ret = TCPCHK_EVAL_STOP;
1955 msg = alloc_trash_chunk();
1956 if (msg)
1957 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1958 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1959 goto out;
1960
1961 wait_more_data:
1962 ret = TCPCHK_EVAL_WAIT;
1963 goto out;
1964}
1965
1966/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1967 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1968 * if an error occurred.
1969 */
1970enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1971{
1972 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1973 struct tcpcheck_expect *expect = &rule->expect;
1974 struct buffer *msg = NULL, *tmp = NULL;
1975 struct ist desc = IST_NULL;
1976 enum healthcheck_status status;
1977 int match, inverse;
1978
Christopher Faulet147b8c92021-04-10 09:00:38 +02001979 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1980
Willy Tarreau51cd5952020-06-05 12:25:38 +02001981 last_read |= b_full(&check->bi);
1982
1983 /* The current expect might need more data than the previous one, check again
1984 * that the minimum amount data required to match is respected.
1985 */
1986 if (!last_read) {
1987 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1988 (b_data(&check->bi) < istlen(expect->data))) {
1989 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001990 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001991 goto out;
1992 }
1993 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1994 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001995 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001996 goto out;
1997 }
1998 }
1999
2000 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
2001 /* Make GCC happy ; initialize match to a failure state. */
2002 match = inverse;
2003 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
2004
2005 switch (expect->type) {
2006 case TCPCHK_EXPECT_STRING:
2007 case TCPCHK_EXPECT_BINARY:
2008 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2009 break;
2010 case TCPCHK_EXPECT_STRING_REGEX:
2011 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2012 break;
2013
2014 case TCPCHK_EXPECT_BINARY_REGEX:
2015 chunk_reset(&trash);
2016 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2017 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2018 break;
2019
2020 case TCPCHK_EXPECT_STRING_LF:
2021 case TCPCHK_EXPECT_BINARY_LF:
2022 match = 0;
2023 tmp = alloc_trash_chunk();
2024 if (!tmp) {
2025 status = HCHK_STATUS_L7RSP;
2026 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002027 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002028 goto error;
2029 }
2030 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2031 if (!b_data(tmp)) {
2032 status = HCHK_STATUS_L7RSP;
2033 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002034 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002035 goto error;
2036 }
2037 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2038 int len = tmp->data;
2039 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2040 status = HCHK_STATUS_L7RSP;
2041 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002042 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002043 goto error;
2044 }
2045 tmp->data = len;
2046 }
2047 if (b_data(&check->bi) < tmp->data) {
2048 if (!last_read) {
2049 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002050 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002051 goto out;
2052 }
2053 break;
2054 }
2055 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2056 break;
2057
2058 case TCPCHK_EXPECT_CUSTOM:
2059 if (expect->custom)
2060 ret = expect->custom(check, rule, last_read);
2061 goto out;
2062 default:
2063 /* Should never happen. */
2064 ret = TCPCHK_EVAL_STOP;
2065 goto out;
2066 }
2067
2068
2069 /* Wait for more data on mismatch only if no minimum is defined (-1),
2070 * otherwise the absence of match is already conclusive.
2071 */
2072 if (!match && !last_read && (expect->min_recv == -1)) {
2073 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002074 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002075 goto out;
2076 }
2077
2078 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002079 if (match ^ inverse) {
2080 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002081 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002082 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002083
2084 error:
2085 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002086 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002087 ret = TCPCHK_EVAL_STOP;
2088 msg = alloc_trash_chunk();
2089 if (msg)
2090 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2091 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2092 free_trash_chunk(msg);
2093
2094 out:
2095 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002096 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002097 return ret;
2098}
2099
2100/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2101 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2102 * waits.
2103 */
2104enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2105{
2106 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2107 struct act_rule *act_rule;
2108 enum act_return act_ret;
2109
2110 act_rule =rule->action_kw.rule;
2111 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2112 if (act_ret != ACT_RET_CONT) {
2113 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2114 tcpcheck_get_step_id(check, rule));
2115 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2116 ret = TCPCHK_EVAL_STOP;
2117 }
2118
2119 return ret;
2120}
2121
2122/* Executes a tcp-check ruleset. Note that this is called both from the
2123 * connection's wake() callback and from the check scheduling task. It returns
2124 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2125 * presenting the risk of an fd replacement.
2126 *
2127 * Please do NOT place any return statement in this function and only leave
2128 * via the out_end_tcpcheck label after setting retcode.
2129 */
2130int tcpcheck_main(struct check *check)
2131{
2132 struct tcpcheck_rule *rule;
2133 struct conn_stream *cs = check->cs;
2134 struct connection *conn = cs_conn(cs);
2135 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002136 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002137 enum tcpcheck_eval_ret eval_ret;
2138
2139 /* here, we know that the check is complete or that it failed */
2140 if (check->result != CHK_RES_UNKNOWN)
2141 goto out;
2142
Christopher Faulet147b8c92021-04-10 09:00:38 +02002143 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2144
Willy Tarreau51cd5952020-06-05 12:25:38 +02002145 /* Note: the conn-stream and the connection may only be undefined before
2146 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002147 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002148 */
2149
2150 /* 1- check for connection error, if any */
2151 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR))
2152 goto out_end_tcpcheck;
2153
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002154 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002155 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002156 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002157 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002158 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2159 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002160
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002161 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002162 * tcp-check variables */
2163 else {
2164 struct tcpcheck_var *var;
2165
2166 /* First evaluation, create a session */
2167 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2168 if (!check->sess) {
2169 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002170 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002171 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2172 goto out_end_tcpcheck;
2173 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002174 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002175 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2176
2177 /* Preset tcp-check variables */
2178 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2179 struct sample smp;
2180
2181 memset(&smp, 0, sizeof(smp));
2182 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2183 smp.data = var->data;
2184 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2185 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002186 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002187 }
2188
2189 /* Now evaluate the tcp-check rules */
2190
2191 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2192 check->code = 0;
2193 switch (rule->action) {
2194 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002195 /* Not the first connection, release it first */
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002196 if (cs && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002197 check->state |= CHK_ST_CLOSE_CONN;
2198 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002199 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002200
2201 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002202
2203 /* We are still waiting the connection gets closed */
2204 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002205 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002206 eval_ret = TCPCHK_EVAL_WAIT;
2207 break;
2208 }
2209
Christopher Faulet147b8c92021-04-10 09:00:38 +02002210 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002211 eval_ret = tcpcheck_eval_connect(check, rule);
2212
2213 /* Refresh conn-stream and connection */
2214 cs = check->cs;
2215 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002216 last_read = 0;
2217 must_read = ((cs && IS_HTX_CS(cs)) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002218 break;
2219 case TCPCHK_ACT_SEND:
2220 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002221 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002222 eval_ret = tcpcheck_eval_send(check, rule);
2223 must_read = 1;
2224 break;
2225 case TCPCHK_ACT_EXPECT:
2226 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002227 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002228 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002229 eval_ret = tcpcheck_eval_recv(check, rule);
2230 if (eval_ret == TCPCHK_EVAL_STOP)
2231 goto out_end_tcpcheck;
2232 else if (eval_ret == TCPCHK_EVAL_WAIT)
2233 goto out;
2234 last_read = ((conn->flags & CO_FL_ERROR) || (cs->flags & (CS_FL_ERROR|CS_FL_EOS)));
2235 must_read = 0;
2236 }
2237
2238 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2239 ? tcpcheck_eval_expect_http(check, rule, last_read)
2240 : tcpcheck_eval_expect(check, rule, last_read));
2241
2242 if (eval_ret == TCPCHK_EVAL_WAIT) {
2243 check->current_step = rule->expect.head;
2244 if (!(check->wait_list.events & SUB_RETRY_RECV))
2245 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2246 }
2247 break;
2248 case TCPCHK_ACT_ACTION_KW:
2249 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002250 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002251 eval_ret = tcpcheck_eval_action_kw(check, rule);
2252 break;
2253 default:
2254 /* Otherwise, just go to the next one and don't update
2255 * the current step
2256 */
2257 eval_ret = TCPCHK_EVAL_CONTINUE;
2258 break;
2259 }
2260
2261 switch (eval_ret) {
2262 case TCPCHK_EVAL_CONTINUE:
2263 break;
2264 case TCPCHK_EVAL_WAIT:
2265 goto out;
2266 case TCPCHK_EVAL_STOP:
2267 goto out_end_tcpcheck;
2268 }
2269 }
2270
2271 /* All rules was evaluated */
2272 if (check->current_step) {
2273 rule = check->current_step;
2274
Christopher Faulet147b8c92021-04-10 09:00:38 +02002275 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2276
Willy Tarreau51cd5952020-06-05 12:25:38 +02002277 if (rule->action == TCPCHK_ACT_EXPECT) {
2278 struct buffer *msg;
2279 enum healthcheck_status status;
2280
2281 if (check->server &&
2282 (check->server->proxy->options & PR_O_DISABLE404) &&
2283 (check->server->next_state != SRV_ST_STOPPED) &&
2284 (check->code == 404)) {
2285 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002286 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002287 goto out_end_tcpcheck;
2288 }
2289
2290 msg = alloc_trash_chunk();
2291 if (msg)
2292 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2293 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2294 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2295 free_trash_chunk(msg);
2296 }
2297 else if (rule->action == TCPCHK_ACT_CONNECT) {
2298 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2299 enum healthcheck_status status = HCHK_STATUS_L4OK;
2300#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002301 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002302 status = HCHK_STATUS_L6OK;
2303#endif
2304 set_server_check_status(check, status, msg);
2305 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002306 else
2307 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002308 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002309 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002310 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002311 }
2312 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002313
2314 out_end_tcpcheck:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002315 if ((conn && conn->flags & CO_FL_ERROR) || (cs && cs->flags & CS_FL_ERROR)) {
2316 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002317 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002318 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002319
Christopher Fauletb381a502020-11-25 13:47:00 +01002320 /* the tcpcheck is finished, release in/out buffer now */
2321 check_release_buf(check, &check->bi);
2322 check_release_buf(check, &check->bo);
2323
Willy Tarreau51cd5952020-06-05 12:25:38 +02002324 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002325 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002326 return retcode;
2327}
2328
2329
2330/**************************************************************************/
2331/******************* Internals to parse tcp-check rules *******************/
2332/**************************************************************************/
2333struct action_kw_list tcp_check_keywords = {
2334 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2335};
2336
2337/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2338 * returned on error.
2339 */
2340struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2341 struct list *rules, struct action_kw *kw,
2342 const char *file, int line, char **errmsg)
2343{
2344 struct tcpcheck_rule *chk = NULL;
2345 struct act_rule *actrule = NULL;
2346
Willy Tarreaud535f802021-10-11 08:49:26 +02002347 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002348 if (!actrule) {
2349 memprintf(errmsg, "out of memory");
2350 goto error;
2351 }
2352 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002353
2354 cur_arg++;
2355 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2356 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2357 goto error;
2358 }
2359
2360 chk = calloc(1, sizeof(*chk));
2361 if (!chk) {
2362 memprintf(errmsg, "out of memory");
2363 goto error;
2364 }
2365 chk->action = TCPCHK_ACT_ACTION_KW;
2366 chk->action_kw.rule = actrule;
2367 return chk;
2368
2369 error:
2370 free(actrule);
2371 return NULL;
2372}
2373
2374/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2375 * returned on error.
2376 */
2377struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2378 const char *file, int line, char **errmsg)
2379{
2380 struct tcpcheck_rule *chk = NULL;
2381 struct sockaddr_storage *sk = NULL;
2382 char *comment = NULL, *sni = NULL, *alpn = NULL;
2383 struct sample_expr *port_expr = NULL;
2384 const struct mux_proto_list *mux_proto = NULL;
2385 unsigned short conn_opts = 0;
2386 long port = 0;
2387 int alpn_len = 0;
2388
2389 list_for_each_entry(chk, rules, list) {
2390 if (chk->action == TCPCHK_ACT_CONNECT)
2391 break;
2392 if (chk->action == TCPCHK_ACT_COMMENT ||
2393 chk->action == TCPCHK_ACT_ACTION_KW ||
2394 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2395 continue;
2396
2397 memprintf(errmsg, "first step MUST also be a 'connect', "
2398 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2399 "when there is a 'connect' step in the tcp-check ruleset");
2400 goto error;
2401 }
2402
2403 cur_arg++;
2404 while (*(args[cur_arg])) {
2405 if (strcmp(args[cur_arg], "default") == 0)
2406 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2407 else if (strcmp(args[cur_arg], "addr") == 0) {
2408 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002409
2410 if (!*(args[cur_arg+1])) {
2411 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2412 goto error;
2413 }
2414
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002415 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2416 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002417 if (!sk) {
2418 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2419 goto error;
2420 }
2421
Willy Tarreau51cd5952020-06-05 12:25:38 +02002422 cur_arg++;
2423 }
2424 else if (strcmp(args[cur_arg], "port") == 0) {
2425 const char *p, *end;
2426
2427 if (!*(args[cur_arg+1])) {
2428 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2429 goto error;
2430 }
2431 cur_arg++;
2432
2433 port = 0;
2434 release_sample_expr(port_expr);
2435 p = args[cur_arg]; end = p + strlen(p);
2436 port = read_uint(&p, end);
2437 if (p != end) {
2438 int idx = 0;
2439
2440 px->conf.args.ctx = ARGC_SRV;
2441 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002442 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002443
2444 if (!port_expr) {
2445 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2446 goto error;
2447 }
2448 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2449 memprintf(errmsg, "error detected while parsing port expression : "
2450 " fetch method '%s' extracts information from '%s', "
2451 "none of which is available here.\n",
2452 args[cur_arg], sample_src_names(port_expr->fetch->use));
2453 goto error;
2454 }
2455 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2456 }
2457 else if (port > 65535 || port < 1) {
2458 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2459 args[cur_arg]);
2460 goto error;
2461 }
2462 }
2463 else if (strcmp(args[cur_arg], "proto") == 0) {
2464 if (!*(args[cur_arg+1])) {
2465 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2466 goto error;
2467 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002468 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002469 if (!mux_proto) {
2470 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2471 goto error;
2472 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002473
2474 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2475 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2476 goto error;
2477 }
2478 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2479 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2480 goto error;
2481 }
2482
Willy Tarreau51cd5952020-06-05 12:25:38 +02002483 cur_arg++;
2484 }
2485 else if (strcmp(args[cur_arg], "comment") == 0) {
2486 if (!*(args[cur_arg+1])) {
2487 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2488 goto error;
2489 }
2490 cur_arg++;
2491 free(comment);
2492 comment = strdup(args[cur_arg]);
2493 if (!comment) {
2494 memprintf(errmsg, "out of memory");
2495 goto error;
2496 }
2497 }
2498 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2499 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2500 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2501 conn_opts |= TCPCHK_OPT_SOCKS4;
2502 else if (strcmp(args[cur_arg], "linger") == 0)
2503 conn_opts |= TCPCHK_OPT_LINGER;
2504#ifdef USE_OPENSSL
2505 else if (strcmp(args[cur_arg], "ssl") == 0) {
2506 px->options |= PR_O_TCPCHK_SSL;
2507 conn_opts |= TCPCHK_OPT_SSL;
2508 }
2509 else if (strcmp(args[cur_arg], "sni") == 0) {
2510 if (!*(args[cur_arg+1])) {
2511 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2512 goto error;
2513 }
2514 cur_arg++;
2515 free(sni);
2516 sni = strdup(args[cur_arg]);
2517 if (!sni) {
2518 memprintf(errmsg, "out of memory");
2519 goto error;
2520 }
2521 }
2522 else if (strcmp(args[cur_arg], "alpn") == 0) {
2523#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2524 free(alpn);
2525 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2526 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2527 goto error;
2528 }
2529 cur_arg++;
2530#else
2531 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2532 goto error;
2533#endif
2534 }
2535#endif /* USE_OPENSSL */
2536
2537 else {
2538 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2539#ifdef USE_OPENSSL
2540 ", 'ssl', 'sni', 'alpn'"
2541#endif /* USE_OPENSSL */
2542 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2543 args[cur_arg]);
2544 goto error;
2545 }
2546 cur_arg++;
2547 }
2548
2549 chk = calloc(1, sizeof(*chk));
2550 if (!chk) {
2551 memprintf(errmsg, "out of memory");
2552 goto error;
2553 }
2554 chk->action = TCPCHK_ACT_CONNECT;
2555 chk->comment = comment;
2556 chk->connect.port = port;
2557 chk->connect.options = conn_opts;
2558 chk->connect.sni = sni;
2559 chk->connect.alpn = alpn;
2560 chk->connect.alpn_len= alpn_len;
2561 chk->connect.port_expr= port_expr;
2562 chk->connect.mux_proto= mux_proto;
2563 if (sk)
2564 chk->connect.addr = *sk;
2565 return chk;
2566
2567 error:
2568 free(alpn);
2569 free(sni);
2570 free(comment);
2571 release_sample_expr(port_expr);
2572 return NULL;
2573}
2574
2575/* Parses and creates a tcp-check send rule. NULL is returned on error */
2576struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2577 const char *file, int line, char **errmsg)
2578{
2579 struct tcpcheck_rule *chk = NULL;
2580 char *comment = NULL, *data = NULL;
2581 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2582
2583 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2584 type = TCPCHK_SEND_BINARY_LF;
2585 else if (strcmp(args[cur_arg], "send-binary") == 0)
2586 type = TCPCHK_SEND_BINARY;
2587 else if (strcmp(args[cur_arg], "send-lf") == 0)
2588 type = TCPCHK_SEND_STRING_LF;
2589 else if (strcmp(args[cur_arg], "send") == 0)
2590 type = TCPCHK_SEND_STRING;
2591
2592 if (!*(args[cur_arg+1])) {
2593 memprintf(errmsg, "'%s' expects a %s as argument",
2594 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2595 goto error;
2596 }
2597
2598 data = args[cur_arg+1];
2599
2600 cur_arg += 2;
2601 while (*(args[cur_arg])) {
2602 if (strcmp(args[cur_arg], "comment") == 0) {
2603 if (!*(args[cur_arg+1])) {
2604 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2605 goto error;
2606 }
2607 cur_arg++;
2608 free(comment);
2609 comment = strdup(args[cur_arg]);
2610 if (!comment) {
2611 memprintf(errmsg, "out of memory");
2612 goto error;
2613 }
2614 }
2615 else {
2616 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2617 args[cur_arg]);
2618 goto error;
2619 }
2620 cur_arg++;
2621 }
2622
2623 chk = calloc(1, sizeof(*chk));
2624 if (!chk) {
2625 memprintf(errmsg, "out of memory");
2626 goto error;
2627 }
2628 chk->action = TCPCHK_ACT_SEND;
2629 chk->comment = comment;
2630 chk->send.type = type;
2631
2632 switch (chk->send.type) {
2633 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002634 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002635 if (!isttest(chk->send.data)) {
2636 memprintf(errmsg, "out of memory");
2637 goto error;
2638 }
2639 break;
2640 case TCPCHK_SEND_BINARY: {
2641 int len = chk->send.data.len;
2642 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2643 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2644 goto error;
2645 }
2646 chk->send.data.len = len;
2647 break;
2648 }
2649 case TCPCHK_SEND_STRING_LF:
2650 case TCPCHK_SEND_BINARY_LF:
2651 LIST_INIT(&chk->send.fmt);
2652 px->conf.args.ctx = ARGC_SRV;
2653 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2654 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2655 goto error;
2656 }
2657 break;
2658 case TCPCHK_SEND_HTTP:
2659 case TCPCHK_SEND_UNDEF:
2660 goto error;
2661 }
2662
2663 return chk;
2664
2665 error:
2666 free(chk);
2667 free(comment);
2668 return NULL;
2669}
2670
2671/* Parses and creates a http-check send rule. NULL is returned on error */
2672struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2673 const char *file, int line, char **errmsg)
2674{
2675 struct tcpcheck_rule *chk = NULL;
2676 struct tcpcheck_http_hdr *hdr = NULL;
2677 struct http_hdr hdrs[global.tune.max_http_hdr];
2678 char *meth = NULL, *uri = NULL, *vsn = NULL;
2679 char *body = NULL, *comment = NULL;
2680 unsigned int flags = 0;
2681 int i = 0, host_hdr = -1;
2682
2683 cur_arg++;
2684 while (*(args[cur_arg])) {
2685 if (strcmp(args[cur_arg], "meth") == 0) {
2686 if (!*(args[cur_arg+1])) {
2687 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2688 goto error;
2689 }
2690 cur_arg++;
2691 meth = args[cur_arg];
2692 }
2693 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2694 if (!*(args[cur_arg+1])) {
2695 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2696 goto error;
2697 }
2698 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2699 if (strcmp(args[cur_arg], "uri-lf") == 0)
2700 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2701 cur_arg++;
2702 uri = args[cur_arg];
2703 }
2704 else if (strcmp(args[cur_arg], "ver") == 0) {
2705 if (!*(args[cur_arg+1])) {
2706 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2707 goto error;
2708 }
2709 cur_arg++;
2710 vsn = args[cur_arg];
2711 }
2712 else if (strcmp(args[cur_arg], "hdr") == 0) {
2713 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2714 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2715 goto error;
2716 }
2717
2718 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2719 if (host_hdr >= 0) {
2720 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2721 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2722 goto error;
2723 }
2724 host_hdr = i;
2725 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002726 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002727 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2728 goto skip_hdr;
2729
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002730 hdrs[i].n = ist(args[cur_arg + 1]);
2731 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002732 i++;
2733 skip_hdr:
2734 cur_arg += 2;
2735 }
2736 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2737 if (!*(args[cur_arg+1])) {
2738 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2739 goto error;
2740 }
2741 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2742 if (strcmp(args[cur_arg], "body-lf") == 0)
2743 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2744 cur_arg++;
2745 body = args[cur_arg];
2746 }
2747 else if (strcmp(args[cur_arg], "comment") == 0) {
2748 if (!*(args[cur_arg+1])) {
2749 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2750 goto error;
2751 }
2752 cur_arg++;
2753 free(comment);
2754 comment = strdup(args[cur_arg]);
2755 if (!comment) {
2756 memprintf(errmsg, "out of memory");
2757 goto error;
2758 }
2759 }
2760 else {
2761 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2762 " but got '%s' as argument.", args[cur_arg]);
2763 goto error;
2764 }
2765 cur_arg++;
2766 }
2767
2768 hdrs[i].n = hdrs[i].v = IST_NULL;
2769
2770 chk = calloc(1, sizeof(*chk));
2771 if (!chk) {
2772 memprintf(errmsg, "out of memory");
2773 goto error;
2774 }
2775 chk->action = TCPCHK_ACT_SEND;
2776 chk->comment = comment; comment = NULL;
2777 chk->send.type = TCPCHK_SEND_HTTP;
2778 chk->send.http.flags = flags;
2779 LIST_INIT(&chk->send.http.hdrs);
2780
2781 if (meth) {
2782 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2783 chk->send.http.meth.str.area = strdup(meth);
2784 chk->send.http.meth.str.data = strlen(meth);
2785 if (!chk->send.http.meth.str.area) {
2786 memprintf(errmsg, "out of memory");
2787 goto error;
2788 }
2789 }
2790 if (uri) {
2791 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2792 LIST_INIT(&chk->send.http.uri_fmt);
2793 px->conf.args.ctx = ARGC_SRV;
2794 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2795 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2796 goto error;
2797 }
2798 }
2799 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002800 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002801 if (!isttest(chk->send.http.uri)) {
2802 memprintf(errmsg, "out of memory");
2803 goto error;
2804 }
2805 }
2806 }
2807 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002808 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002809 if (!isttest(chk->send.http.vsn)) {
2810 memprintf(errmsg, "out of memory");
2811 goto error;
2812 }
2813 }
2814 for (i = 0; istlen(hdrs[i].n); i++) {
2815 hdr = calloc(1, sizeof(*hdr));
2816 if (!hdr) {
2817 memprintf(errmsg, "out of memory");
2818 goto error;
2819 }
2820 LIST_INIT(&hdr->value);
2821 hdr->name = istdup(hdrs[i].n);
2822 if (!isttest(hdr->name)) {
2823 memprintf(errmsg, "out of memory");
2824 goto error;
2825 }
2826
2827 ist0(hdrs[i].v);
2828 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2829 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002830 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002831 hdr = NULL;
2832 }
2833
2834 if (body) {
2835 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2836 LIST_INIT(&chk->send.http.body_fmt);
2837 px->conf.args.ctx = ARGC_SRV;
2838 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2839 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2840 goto error;
2841 }
2842 }
2843 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002844 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002845 if (!isttest(chk->send.http.body)) {
2846 memprintf(errmsg, "out of memory");
2847 goto error;
2848 }
2849 }
2850 }
2851
2852 return chk;
2853
2854 error:
2855 free_tcpcheck_http_hdr(hdr);
2856 free_tcpcheck(chk, 0);
2857 free(comment);
2858 return NULL;
2859}
2860
2861/* Parses and creates a http-check comment rule. NULL is returned on error */
2862struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2863 const char *file, int line, char **errmsg)
2864{
2865 struct tcpcheck_rule *chk = NULL;
2866 char *comment = NULL;
2867
2868 if (!*(args[cur_arg+1])) {
2869 memprintf(errmsg, "expects a string as argument");
2870 goto error;
2871 }
2872 cur_arg++;
2873 comment = strdup(args[cur_arg]);
2874 if (!comment) {
2875 memprintf(errmsg, "out of memory");
2876 goto error;
2877 }
2878
2879 chk = calloc(1, sizeof(*chk));
2880 if (!chk) {
2881 memprintf(errmsg, "out of memory");
2882 goto error;
2883 }
2884 chk->action = TCPCHK_ACT_COMMENT;
2885 chk->comment = comment;
2886 return chk;
2887
2888 error:
2889 free(comment);
2890 return NULL;
2891}
2892
2893/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2894 * on error. <proto> is set to the right protocol flags (covered by the
2895 * TCPCHK_RULES_PROTO_CHK mask).
2896 */
2897struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2898 struct list *rules, unsigned int proto,
2899 const char *file, int line, char **errmsg)
2900{
2901 struct tcpcheck_rule *prev_check, *chk = NULL;
2902 struct sample_expr *status_expr = NULL;
2903 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2904 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2905 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2906 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2907 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2908 unsigned int flags = 0;
2909 long min_recv = -1;
2910 int inverse = 0;
2911
2912 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2913 if (!*(args[cur_arg+1])) {
2914 memprintf(errmsg, "expects at least a matching pattern as arguments");
2915 goto error;
2916 }
2917
2918 cur_arg++;
2919 while (*(args[cur_arg])) {
2920 int in_pattern = 0;
2921
2922 rescan:
2923 if (strcmp(args[cur_arg], "min-recv") == 0) {
2924 if (in_pattern) {
2925 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2926 goto error;
2927 }
2928 if (!*(args[cur_arg+1])) {
2929 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2930 goto error;
2931 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002932 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002933 cur_arg++;
2934 min_recv = atol(args[cur_arg]);
2935 if (min_recv < -1 || min_recv > INT_MAX) {
2936 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2937 goto error;
2938 }
2939 }
2940 else if (*(args[cur_arg]) == '!') {
2941 in_pattern = 1;
2942 while (*(args[cur_arg]) == '!') {
2943 inverse = !inverse;
2944 args[cur_arg]++;
2945 }
2946 if (!*(args[cur_arg]))
2947 cur_arg++;
2948 goto rescan;
2949 }
2950 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2951 if (type != TCPCHK_EXPECT_UNDEF) {
2952 memprintf(errmsg, "only on pattern expected");
2953 goto error;
2954 }
2955 if (proto != TCPCHK_RULES_HTTP_CHK)
2956 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2957 else
2958 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2959
2960 if (!*(args[cur_arg+1])) {
2961 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2962 goto error;
2963 }
2964 cur_arg++;
2965 pattern = args[cur_arg];
2966 }
2967 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2968 if (proto == TCPCHK_RULES_HTTP_CHK)
2969 goto bad_http_kw;
2970 if (type != TCPCHK_EXPECT_UNDEF) {
2971 memprintf(errmsg, "only on pattern expected");
2972 goto error;
2973 }
2974 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2975
2976 if (!*(args[cur_arg+1])) {
2977 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2978 goto error;
2979 }
2980 cur_arg++;
2981 pattern = args[cur_arg];
2982 }
2983 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2984 if (type != TCPCHK_EXPECT_UNDEF) {
2985 memprintf(errmsg, "only on pattern expected");
2986 goto error;
2987 }
2988 if (proto != TCPCHK_RULES_HTTP_CHK)
2989 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2990 else {
2991 if (*(args[cur_arg]) != 's')
2992 goto bad_http_kw;
2993 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2994 }
2995
2996 if (!*(args[cur_arg+1])) {
2997 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2998 goto error;
2999 }
3000 cur_arg++;
3001 pattern = args[cur_arg];
3002 }
3003 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3004 if (proto != TCPCHK_RULES_HTTP_CHK)
3005 goto bad_tcp_kw;
3006 if (type != TCPCHK_EXPECT_UNDEF) {
3007 memprintf(errmsg, "only on pattern expected");
3008 goto error;
3009 }
3010 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3011
3012 if (!*(args[cur_arg+1])) {
3013 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3014 goto error;
3015 }
3016 cur_arg++;
3017 pattern = args[cur_arg];
3018 }
3019 else if (strcmp(args[cur_arg], "custom") == 0) {
3020 if (in_pattern) {
3021 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3022 goto error;
3023 }
3024 if (type != TCPCHK_EXPECT_UNDEF) {
3025 memprintf(errmsg, "only on pattern expected");
3026 goto error;
3027 }
3028 type = TCPCHK_EXPECT_CUSTOM;
3029 }
3030 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3031 int orig_arg = cur_arg;
3032
3033 if (proto != TCPCHK_RULES_HTTP_CHK)
3034 goto bad_tcp_kw;
3035 if (type != TCPCHK_EXPECT_UNDEF) {
3036 memprintf(errmsg, "only on pattern expected");
3037 goto error;
3038 }
3039 type = TCPCHK_EXPECT_HTTP_HEADER;
3040
3041 if (strcmp(args[cur_arg], "fhdr") == 0)
3042 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3043
3044 /* Parse the name pattern, mandatory */
3045 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3046 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3047 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3048 args[orig_arg]);
3049 goto error;
3050 }
3051
3052 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3053 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3054
3055 cur_arg += 2;
3056 if (strcmp(args[cur_arg], "-m") == 0) {
3057 if (!*(args[cur_arg+1])) {
3058 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3059 args[orig_arg], args[cur_arg]);
3060 goto error;
3061 }
3062 if (strcmp(args[cur_arg+1], "str") == 0)
3063 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3064 else if (strcmp(args[cur_arg+1], "beg") == 0)
3065 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3066 else if (strcmp(args[cur_arg+1], "end") == 0)
3067 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3068 else if (strcmp(args[cur_arg+1], "sub") == 0)
3069 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3070 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3071 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3072 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3073 args[orig_arg]);
3074 goto error;
3075 }
3076 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3077 }
3078 else {
3079 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3080 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3081 goto error;
3082 }
3083 cur_arg += 2;
3084 }
3085 else
3086 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3087 npat = args[cur_arg];
3088
3089 if (!*(args[cur_arg+1]) ||
3090 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3091 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3092 goto next;
3093 }
3094 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3095 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3096
3097 /* Parse the value pattern, optional */
3098 if (strcmp(args[cur_arg+2], "-m") == 0) {
3099 cur_arg += 2;
3100 if (!*(args[cur_arg+1])) {
3101 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3102 args[orig_arg], args[cur_arg]);
3103 goto error;
3104 }
3105 if (strcmp(args[cur_arg+1], "str") == 0)
3106 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3107 else if (strcmp(args[cur_arg+1], "beg") == 0)
3108 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3109 else if (strcmp(args[cur_arg+1], "end") == 0)
3110 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3111 else if (strcmp(args[cur_arg+1], "sub") == 0)
3112 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3113 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3114 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3115 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3116 args[orig_arg]);
3117 goto error;
3118 }
3119 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3120 }
3121 else {
3122 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3123 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3124 goto error;
3125 }
3126 }
3127 else
3128 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3129
3130 if (!*(args[cur_arg+2])) {
3131 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3132 goto error;
3133 }
3134 vpat = args[cur_arg+2];
3135 cur_arg += 2;
3136 }
3137 else if (strcmp(args[cur_arg], "comment") == 0) {
3138 if (in_pattern) {
3139 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3140 goto error;
3141 }
3142 if (!*(args[cur_arg+1])) {
3143 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3144 goto error;
3145 }
3146 cur_arg++;
3147 free(comment);
3148 comment = strdup(args[cur_arg]);
3149 if (!comment) {
3150 memprintf(errmsg, "out of memory");
3151 goto error;
3152 }
3153 }
3154 else if (strcmp(args[cur_arg], "on-success") == 0) {
3155 if (in_pattern) {
3156 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3157 goto error;
3158 }
3159 if (!*(args[cur_arg+1])) {
3160 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3161 goto error;
3162 }
3163 cur_arg++;
3164 on_success_msg = args[cur_arg];
3165 }
3166 else if (strcmp(args[cur_arg], "on-error") == 0) {
3167 if (in_pattern) {
3168 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3169 goto error;
3170 }
3171 if (!*(args[cur_arg+1])) {
3172 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3173 goto error;
3174 }
3175 cur_arg++;
3176 on_error_msg = args[cur_arg];
3177 }
3178 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3179 if (in_pattern) {
3180 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3181 goto error;
3182 }
3183 if (!*(args[cur_arg+1])) {
3184 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3185 goto error;
3186 }
3187 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3188 ok_st = HCHK_STATUS_L7OKD;
3189 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3190 ok_st = HCHK_STATUS_L7OKCD;
3191 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3192 ok_st = HCHK_STATUS_L6OK;
3193 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3194 ok_st = HCHK_STATUS_L4OK;
3195 else {
3196 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3197 args[cur_arg], args[cur_arg+1]);
3198 goto error;
3199 }
3200 cur_arg++;
3201 }
3202 else if (strcmp(args[cur_arg], "error-status") == 0) {
3203 if (in_pattern) {
3204 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3205 goto error;
3206 }
3207 if (!*(args[cur_arg+1])) {
3208 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3209 goto error;
3210 }
3211 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3212 err_st = HCHK_STATUS_L7RSP;
3213 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3214 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003215 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3216 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003217 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3218 err_st = HCHK_STATUS_L6RSP;
3219 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3220 err_st = HCHK_STATUS_L4CON;
3221 else {
3222 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3223 args[cur_arg], args[cur_arg+1]);
3224 goto error;
3225 }
3226 cur_arg++;
3227 }
3228 else if (strcmp(args[cur_arg], "status-code") == 0) {
3229 int idx = 0;
3230
3231 if (in_pattern) {
3232 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3233 goto error;
3234 }
3235 if (!*(args[cur_arg+1])) {
3236 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3237 goto error;
3238 }
3239
3240 cur_arg++;
3241 release_sample_expr(status_expr);
3242 px->conf.args.ctx = ARGC_SRV;
3243 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003244 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003245 if (!status_expr) {
3246 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3247 goto error;
3248 }
3249 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3250 memprintf(errmsg, "error detected while parsing status-code expression : "
3251 " fetch method '%s' extracts information from '%s', "
3252 "none of which is available here.\n",
3253 args[cur_arg], sample_src_names(status_expr->fetch->use));
3254 goto error;
3255 }
3256 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3257 }
3258 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3259 if (in_pattern) {
3260 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3261 goto error;
3262 }
3263 if (!*(args[cur_arg+1])) {
3264 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3265 goto error;
3266 }
3267 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3268 tout_st = HCHK_STATUS_L7TOUT;
3269 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3270 tout_st = HCHK_STATUS_L6TOUT;
3271 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3272 tout_st = HCHK_STATUS_L4TOUT;
3273 else {
3274 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3275 args[cur_arg], args[cur_arg+1]);
3276 goto error;
3277 }
3278 cur_arg++;
3279 }
3280 else {
3281 if (proto == TCPCHK_RULES_HTTP_CHK) {
3282 bad_http_kw:
3283 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3284 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3285 }
3286 else {
3287 bad_tcp_kw:
3288 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3289 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3290 }
3291 goto error;
3292 }
3293 next:
3294 cur_arg++;
3295 }
3296
3297 chk = calloc(1, sizeof(*chk));
3298 if (!chk) {
3299 memprintf(errmsg, "out of memory");
3300 goto error;
3301 }
3302 chk->action = TCPCHK_ACT_EXPECT;
3303 LIST_INIT(&chk->expect.onerror_fmt);
3304 LIST_INIT(&chk->expect.onsuccess_fmt);
3305 chk->comment = comment; comment = NULL;
3306 chk->expect.type = type;
3307 chk->expect.min_recv = min_recv;
3308 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3309 chk->expect.ok_status = ok_st;
3310 chk->expect.err_status = err_st;
3311 chk->expect.tout_status = tout_st;
3312 chk->expect.status_expr = status_expr; status_expr = NULL;
3313
3314 if (on_success_msg) {
3315 px->conf.args.ctx = ARGC_SRV;
3316 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3317 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3318 goto error;
3319 }
3320 }
3321 if (on_error_msg) {
3322 px->conf.args.ctx = ARGC_SRV;
3323 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3324 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3325 goto error;
3326 }
3327 }
3328
3329 switch (chk->expect.type) {
3330 case TCPCHK_EXPECT_HTTP_STATUS: {
3331 const char *p = pattern;
3332 unsigned int c1,c2;
3333
3334 chk->expect.codes.codes = NULL;
3335 chk->expect.codes.num = 0;
3336 while (1) {
3337 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3338 if (*p == '-') {
3339 p++;
3340 c2 = read_uint(&p, pattern + strlen(pattern));
3341 }
3342 if (c1 > c2) {
3343 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3344 goto error;
3345 }
3346
3347 chk->expect.codes.num++;
3348 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3349 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3350 if (!chk->expect.codes.codes) {
3351 memprintf(errmsg, "out of memory");
3352 goto error;
3353 }
3354 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3355 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3356
3357 if (*p == '\0')
3358 break;
3359 if (*p != ',') {
3360 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3361 goto error;
3362 }
3363 p++;
3364 }
3365 break;
3366 }
3367 case TCPCHK_EXPECT_STRING:
3368 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003369 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003370 if (!isttest(chk->expect.data)) {
3371 memprintf(errmsg, "out of memory");
3372 goto error;
3373 }
3374 break;
3375 case TCPCHK_EXPECT_BINARY: {
3376 int len = chk->expect.data.len;
3377
3378 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3379 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3380 goto error;
3381 }
3382 chk->expect.data.len = len;
3383 break;
3384 }
3385 case TCPCHK_EXPECT_STRING_REGEX:
3386 case TCPCHK_EXPECT_BINARY_REGEX:
3387 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3388 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3389 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3390 if (!chk->expect.regex)
3391 goto error;
3392 break;
3393
3394 case TCPCHK_EXPECT_STRING_LF:
3395 case TCPCHK_EXPECT_BINARY_LF:
3396 case TCPCHK_EXPECT_HTTP_BODY_LF:
3397 LIST_INIT(&chk->expect.fmt);
3398 px->conf.args.ctx = ARGC_SRV;
3399 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3400 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3401 goto error;
3402 }
3403 break;
3404
3405 case TCPCHK_EXPECT_HTTP_HEADER:
3406 if (!npat) {
3407 memprintf(errmsg, "unexpected error, undefined header name pattern");
3408 goto error;
3409 }
3410 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3411 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3412 if (!chk->expect.hdr.name_re)
3413 goto error;
3414 }
3415 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3416 px->conf.args.ctx = ARGC_SRV;
3417 LIST_INIT(&chk->expect.hdr.name_fmt);
3418 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3419 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3420 goto error;
3421 }
3422 }
3423 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003424 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003425 if (!isttest(chk->expect.hdr.name)) {
3426 memprintf(errmsg, "out of memory");
3427 goto error;
3428 }
3429 }
3430
3431 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3432 chk->expect.hdr.value = IST_NULL;
3433 break;
3434 }
3435
3436 if (!vpat) {
3437 memprintf(errmsg, "unexpected error, undefined header value pattern");
3438 goto error;
3439 }
3440 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3441 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3442 if (!chk->expect.hdr.value_re)
3443 goto error;
3444 }
3445 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3446 px->conf.args.ctx = ARGC_SRV;
3447 LIST_INIT(&chk->expect.hdr.value_fmt);
3448 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3449 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3450 goto error;
3451 }
3452 }
3453 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003454 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003455 if (!isttest(chk->expect.hdr.value)) {
3456 memprintf(errmsg, "out of memory");
3457 goto error;
3458 }
3459 }
3460
3461 break;
3462 case TCPCHK_EXPECT_CUSTOM:
3463 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3464 break;
3465 case TCPCHK_EXPECT_UNDEF:
3466 memprintf(errmsg, "pattern not found");
3467 goto error;
3468 }
3469
3470 /* All tcp-check expect points back to the first inverse expect rule in
3471 * a chain of one or more expect rule, potentially itself.
3472 */
3473 chk->expect.head = chk;
3474 list_for_each_entry_rev(prev_check, rules, list) {
3475 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3476 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3477 chk->expect.head = prev_check;
3478 continue;
3479 }
3480 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3481 break;
3482 }
3483 return chk;
3484
3485 error:
3486 free_tcpcheck(chk, 0);
3487 free(comment);
3488 release_sample_expr(status_expr);
3489 return NULL;
3490}
3491
3492/* Overwrites fields of the old http send rule with those of the new one. When
3493 * replaced, old values are freed and replaced by the new ones. New values are
3494 * not copied but transferred. At the end <new> should be empty and can be
3495 * safely released. This function never fails.
3496 */
3497void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3498{
3499 struct logformat_node *lf, *lfb;
3500 struct tcpcheck_http_hdr *hdr, *bhdr;
3501
3502
3503 if (new->send.http.meth.str.area) {
3504 free(old->send.http.meth.str.area);
3505 old->send.http.meth.meth = new->send.http.meth.meth;
3506 old->send.http.meth.str.area = new->send.http.meth.str.area;
3507 old->send.http.meth.str.data = new->send.http.meth.str.data;
3508 new->send.http.meth.str = BUF_NULL;
3509 }
3510
3511 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3512 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3513 istfree(&old->send.http.uri);
3514 else
3515 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3516 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3517 old->send.http.uri = new->send.http.uri;
3518 new->send.http.uri = IST_NULL;
3519 }
3520 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3521 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3522 istfree(&old->send.http.uri);
3523 else
3524 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3525 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3526 LIST_INIT(&old->send.http.uri_fmt);
3527 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003528 LIST_DELETE(&lf->list);
3529 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003530 }
3531 }
3532
3533 if (isttest(new->send.http.vsn)) {
3534 istfree(&old->send.http.vsn);
3535 old->send.http.vsn = new->send.http.vsn;
3536 new->send.http.vsn = IST_NULL;
3537 }
3538
3539 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3540 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003541 LIST_DELETE(&hdr->list);
3542 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003543 }
3544
3545 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3546 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3547 istfree(&old->send.http.body);
3548 else
3549 free_tcpcheck_fmt(&old->send.http.body_fmt);
3550 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3551 old->send.http.body = new->send.http.body;
3552 new->send.http.body = IST_NULL;
3553 }
3554 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3555 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3556 istfree(&old->send.http.body);
3557 else
3558 free_tcpcheck_fmt(&old->send.http.body_fmt);
3559 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3560 LIST_INIT(&old->send.http.body_fmt);
3561 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003562 LIST_DELETE(&lf->list);
3563 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003564 }
3565 }
3566}
3567
3568/* Internal function used to add an http-check rule in a list during the config
3569 * parsing step. Depending on its type, and the previously inserted rules, a
3570 * specific action may be performed or an error may be reported. This functions
3571 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3572 * message.
3573 */
3574int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3575{
3576 struct tcpcheck_rule *r;
3577
3578 /* the implicit send rule coming from an "option httpchk" line must be
3579 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003580 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003581 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003582 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003583 * sure the ruleset remains valid.
3584 */
3585
3586 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3587 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3588 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3589 * following tests are performed :
3590 *
3591 * 1- If there is no such rule or if it is not a send rule, the implicit send
3592 * rule is pushed in front of the ruleset
3593 *
3594 * 2- If it is another implicit send rule, it is replaced with the new one.
3595 *
3596 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3597 * both, overwriting the old send rule (the explicit one) with info of the
3598 * new send rule (the implicit one).
3599 */
3600 r = get_first_tcpcheck_rule(rules);
3601 if (r && r->action == TCPCHK_ACT_CONNECT)
3602 r = get_next_tcpcheck_rule(rules, r);
3603 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003604 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003605 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003606 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003607 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003608 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003609 }
3610 else {
3611 tcpcheck_overwrite_send_http_rule(r, chk);
3612 free_tcpcheck(chk, 0);
3613 }
3614 }
3615 else {
3616 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3617 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3618 * with an existing implicit send rule, if any. At the end, if there is no error,
3619 * the rule is appended to the list.
3620 */
3621
3622 r = get_last_tcpcheck_rule(rules);
3623 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3624 /* no error */;
3625 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3626 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3627 chk->index+1);
3628 return 0;
3629 }
3630 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3631 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3632 chk->index+1);
3633 return 0;
3634 }
3635 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3636 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3637 chk->index+1);
3638 return 0;
3639 }
3640
3641 if (chk->action == TCPCHK_ACT_SEND) {
3642 r = get_first_tcpcheck_rule(rules);
3643 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3644 tcpcheck_overwrite_send_http_rule(r, chk);
3645 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003646 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003647 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3648 chk = r;
3649 }
3650 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003651 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003652 }
3653 return 1;
3654}
3655
3656/* Check tcp-check health-check configuration for the proxy <px>. */
3657static int check_proxy_tcpcheck(struct proxy *px)
3658{
3659 struct tcpcheck_rule *chk, *back;
3660 char *comment = NULL, *errmsg = NULL;
3661 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003662 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003663
3664 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3665 deinit_proxy_tcpcheck(px);
3666 goto out;
3667 }
3668
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003669 ha_free(&px->check_command);
3670 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003671
3672 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003673 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003674 ret |= ERR_ALERT | ERR_FATAL;
3675 goto out;
3676 }
3677
3678 /* HTTP ruleset only : */
3679 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3680 struct tcpcheck_rule *next;
3681
3682 /* move remaining implicit send rule from "option httpchk" line to the right place.
3683 * If such rule exists, it must be the first one. In this case, the rule is moved
3684 * after the first connect rule, if any. Otherwise, nothing is done.
3685 */
3686 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3687 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3688 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3689 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003690 LIST_DELETE(&chk->list);
3691 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003692 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003693 }
3694 }
3695
3696 /* add implicit expect rule if the last one is a send. It is inherited from previous
3697 * versions where the http expect rule was optional. Now it is possible to chained
3698 * send/expect rules but the last expect may still be implicit.
3699 */
3700 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3701 if (chk && chk->action == TCPCHK_ACT_SEND) {
3702 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3703 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3704 px->conf.file, px->conf.line, &errmsg);
3705 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003706 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003707 "(%s).\n", px->id, errmsg);
3708 free(errmsg);
3709 ret |= ERR_ALERT | ERR_FATAL;
3710 goto out;
3711 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003712 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003713 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003714 }
3715 }
3716
3717 /* For all ruleset: */
3718
3719 /* If there is no connect rule preceding all send / expect rules, an
3720 * implicit one is inserted before all others.
3721 */
3722 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3723 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3724 chk = calloc(1, sizeof(*chk));
3725 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003726 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003727 "(out of memory).\n", px->id);
3728 ret |= ERR_ALERT | ERR_FATAL;
3729 goto out;
3730 }
3731 chk->action = TCPCHK_ACT_CONNECT;
3732 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003733 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003734 }
3735
3736 /* Remove all comment rules. To do so, when a such rule is found, the
3737 * comment is assigned to the following rule(s).
3738 */
3739 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003740 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3741 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003742
3743 prev_action = chk->action;
3744 switch (chk->action) {
3745 case TCPCHK_ACT_COMMENT:
3746 free(comment);
3747 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003748 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003749 free(chk);
3750 break;
3751 case TCPCHK_ACT_CONNECT:
3752 if (!chk->comment && comment)
3753 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003754 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003755 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003756 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003757 break;
3758 case TCPCHK_ACT_SEND:
3759 case TCPCHK_ACT_EXPECT:
3760 if (!chk->comment && comment)
3761 chk->comment = strdup(comment);
3762 break;
3763 }
3764 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003765 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003766
3767 out:
3768 return ret;
3769}
3770
3771void deinit_proxy_tcpcheck(struct proxy *px)
3772{
3773 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3774 px->tcpcheck_rules.flags = 0;
3775 px->tcpcheck_rules.list = NULL;
3776}
3777
3778static void deinit_tcpchecks()
3779{
3780 struct tcpcheck_ruleset *rs;
3781 struct tcpcheck_rule *r, *rb;
3782 struct ebpt_node *node, *next;
3783
3784 node = ebpt_first(&shared_tcpchecks);
3785 while (node) {
3786 next = ebpt_next(node);
3787 ebpt_delete(node);
3788 free(node->key);
3789 rs = container_of(node, typeof(*rs), node);
3790 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003791 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003792 free_tcpcheck(r, 0);
3793 }
3794 free(rs);
3795 node = next;
3796 }
3797}
3798
3799int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3800{
3801 struct tcpcheck_rule *tcpcheck, *prev_check;
3802 struct tcpcheck_expect *expect;
3803
Willy Tarreau6922e552021-03-22 21:11:45 +01003804 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003805 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003806 tcpcheck->action = TCPCHK_ACT_EXPECT;
3807
3808 expect = &tcpcheck->expect;
3809 expect->type = TCPCHK_EXPECT_STRING;
3810 LIST_INIT(&expect->onerror_fmt);
3811 LIST_INIT(&expect->onsuccess_fmt);
3812 expect->ok_status = HCHK_STATUS_L7OKD;
3813 expect->err_status = HCHK_STATUS_L7RSP;
3814 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003815 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003816 if (!isttest(expect->data)) {
3817 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3818 return 0;
3819 }
3820
3821 /* All tcp-check expect points back to the first inverse expect rule
3822 * in a chain of one or more expect rule, potentially itself.
3823 */
3824 tcpcheck->expect.head = tcpcheck;
3825 list_for_each_entry_rev(prev_check, rules->list, list) {
3826 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3827 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3828 tcpcheck->expect.head = prev_check;
3829 continue;
3830 }
3831 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3832 break;
3833 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003834 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003835 return 1;
3836}
3837
3838int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3839{
3840 struct tcpcheck_rule *tcpcheck;
3841 struct tcpcheck_send *send;
3842 const char *in;
3843 char *dst;
3844 int i;
3845
Willy Tarreau6922e552021-03-22 21:11:45 +01003846 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003847 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003848 tcpcheck->action = TCPCHK_ACT_SEND;
3849
3850 send = &tcpcheck->send;
3851 send->type = TCPCHK_SEND_STRING;
3852
3853 for (i = 0; strs[i]; i++)
3854 send->data.len += strlen(strs[i]);
3855
3856 send->data.ptr = malloc(istlen(send->data) + 1);
3857 if (!isttest(send->data)) {
3858 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3859 return 0;
3860 }
3861
3862 dst = istptr(send->data);
3863 for (i = 0; strs[i]; i++)
3864 for (in = strs[i]; (*dst = *in++); dst++);
3865 *dst = 0;
3866
Willy Tarreau2b718102021-04-21 07:32:39 +02003867 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003868 return 1;
3869}
3870
3871/* Parses the "tcp-check" proxy keyword */
3872static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003873 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003874 char **errmsg)
3875{
3876 struct tcpcheck_ruleset *rs = NULL;
3877 struct tcpcheck_rule *chk = NULL;
3878 int index, cur_arg, ret = 0;
3879
3880 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3881 ret = 1;
3882
3883 /* Deduce the ruleset name from the proxy info */
3884 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3885 ((curpx == defpx) ? "defaults" : curpx->id),
3886 curpx->conf.file, curpx->conf.line);
3887
3888 rs = find_tcpcheck_ruleset(b_orig(&trash));
3889 if (rs == NULL) {
3890 rs = create_tcpcheck_ruleset(b_orig(&trash));
3891 if (rs == NULL) {
3892 memprintf(errmsg, "out of memory.\n");
3893 goto error;
3894 }
3895 }
3896
3897 index = 0;
3898 if (!LIST_ISEMPTY(&rs->rules)) {
3899 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3900 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003901 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003902 }
3903
3904 cur_arg = 1;
3905 if (strcmp(args[cur_arg], "connect") == 0)
3906 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3907 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3908 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3909 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3910 else if (strcmp(args[cur_arg], "expect") == 0)
3911 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3912 else if (strcmp(args[cur_arg], "comment") == 0)
3913 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3914 else {
3915 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3916
3917 if (!kw) {
3918 action_kw_tcp_check_build_list(&trash);
3919 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3920 "%s%s. but got '%s'",
3921 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3922 goto error;
3923 }
3924 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3925 }
3926
3927 if (!chk) {
3928 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3929 goto error;
3930 }
3931 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3932
3933 /* No error: add the tcp-check rule in the list */
3934 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003935 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003936
3937 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3938 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3939 /* Use this ruleset if the proxy already has tcp-check enabled */
3940 curpx->tcpcheck_rules.list = &rs->rules;
3941 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3942 }
3943 else {
3944 /* mark this ruleset as unused for now */
3945 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3946 }
3947
3948 return ret;
3949
3950 error:
3951 free_tcpcheck(chk, 0);
3952 free_tcpcheck_ruleset(rs);
3953 return -1;
3954}
3955
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003956/* Parses the "http-check" proxy keyword */
3957static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003958 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003959 char **errmsg)
3960{
3961 struct tcpcheck_ruleset *rs = NULL;
3962 struct tcpcheck_rule *chk = NULL;
3963 int index, cur_arg, ret = 0;
3964
3965 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3966 ret = 1;
3967
3968 cur_arg = 1;
3969 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3970 /* enable a graceful server shutdown on an HTTP 404 response */
3971 curpx->options |= PR_O_DISABLE404;
3972 if (too_many_args(1, args, errmsg, NULL))
3973 goto error;
3974 goto out;
3975 }
3976 else if (strcmp(args[cur_arg], "send-state") == 0) {
3977 /* enable emission of the apparent state of a server in HTTP checks */
3978 curpx->options2 |= PR_O2_CHK_SNDST;
3979 if (too_many_args(1, args, errmsg, NULL))
3980 goto error;
3981 goto out;
3982 }
3983
3984 /* Deduce the ruleset name from the proxy info */
3985 chunk_printf(&trash, "*http-check-%s_%s-%d",
3986 ((curpx == defpx) ? "defaults" : curpx->id),
3987 curpx->conf.file, curpx->conf.line);
3988
3989 rs = find_tcpcheck_ruleset(b_orig(&trash));
3990 if (rs == NULL) {
3991 rs = create_tcpcheck_ruleset(b_orig(&trash));
3992 if (rs == NULL) {
3993 memprintf(errmsg, "out of memory.\n");
3994 goto error;
3995 }
3996 }
3997
3998 index = 0;
3999 if (!LIST_ISEMPTY(&rs->rules)) {
4000 chk = LIST_PREV(&rs->rules, typeof(chk), list);
4001 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4002 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004003 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004004 }
4005
4006 if (strcmp(args[cur_arg], "connect") == 0)
4007 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4008 else if (strcmp(args[cur_arg], "send") == 0)
4009 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4010 else if (strcmp(args[cur_arg], "expect") == 0)
4011 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4012 file, line, errmsg);
4013 else if (strcmp(args[cur_arg], "comment") == 0)
4014 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4015 else {
4016 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4017
4018 if (!kw) {
4019 action_kw_tcp_check_build_list(&trash);
4020 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4021 " 'send', 'expect'%s%s. but got '%s'",
4022 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4023 goto error;
4024 }
4025 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4026 }
4027
4028 if (!chk) {
4029 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4030 goto error;
4031 }
4032 ret = (*errmsg != NULL); /* Handle warning */
4033
4034 chk->index = index;
4035 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4036 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4037 /* Use this ruleset if the proxy already has http-check enabled */
4038 curpx->tcpcheck_rules.list = &rs->rules;
4039 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4040 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4041 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4042 curpx->tcpcheck_rules.list = NULL;
4043 goto error;
4044 }
4045 }
4046 else {
4047 /* mark this ruleset as unused for now */
4048 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004049 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004050 }
4051
4052 out:
4053 return ret;
4054
4055 error:
4056 free_tcpcheck(chk, 0);
4057 free_tcpcheck_ruleset(rs);
4058 return -1;
4059}
4060
4061/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004062int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004063 const char *file, int line)
4064{
4065 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4066 static char *redis_res = "+PONG\r\n";
4067
4068 struct tcpcheck_ruleset *rs = NULL;
4069 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4070 struct tcpcheck_rule *chk;
4071 char *errmsg = NULL;
4072 int err_code = 0;
4073
4074 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4075 err_code |= ERR_WARN;
4076
4077 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4078 goto out;
4079
4080 curpx->options2 &= ~PR_O2_CHK_ANY;
4081 curpx->options2 |= PR_O2_TCPCHK_CHK;
4082
4083 free_tcpcheck_vars(&rules->preset_vars);
4084 rules->list = NULL;
4085 rules->flags = 0;
4086
4087 rs = find_tcpcheck_ruleset("*redis-check");
4088 if (rs)
4089 goto ruleset_found;
4090
4091 rs = create_tcpcheck_ruleset("*redis-check");
4092 if (rs == NULL) {
4093 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4094 goto error;
4095 }
4096
4097 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4098 1, curpx, &rs->rules, file, line, &errmsg);
4099 if (!chk) {
4100 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4101 goto error;
4102 }
4103 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004104 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004105
4106 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4107 "error-status", "L7STS",
4108 "on-error", "%[res.payload(0,0),cut_crlf]",
4109 "on-success", "Redis server is ok",
4110 ""},
4111 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4112 if (!chk) {
4113 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4114 goto error;
4115 }
4116 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004117 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004118
4119 ruleset_found:
4120 rules->list = &rs->rules;
4121 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4122 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4123
4124 out:
4125 free(errmsg);
4126 return err_code;
4127
4128 error:
4129 free_tcpcheck_ruleset(rs);
4130 err_code |= ERR_ALERT | ERR_FATAL;
4131 goto out;
4132}
4133
4134
4135/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004136int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004137 const char *file, int line)
4138{
4139 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4140 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4141 *
4142 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4143 */
4144 static char sslv3_client_hello[] = {
4145 "16" /* ContentType : 0x16 = Handshake */
4146 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4147 "0079" /* ContentLength : 0x79 bytes after this one */
4148 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4149 "000075" /* HandshakeLength : 0x75 bytes after this one */
4150 "0300" /* Hello Version : 0x0300 = v3 */
4151 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4152 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4153 "00" /* Session ID length : empty (no session ID) */
4154 "004E" /* Cipher Suite Length : 78 bytes after this one */
4155 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4156 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4157 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4158 "000D" "000E" "000F" "0010" /* various bit lengths, */
4159 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4160 "0015" "0016" "0017" "0018"
4161 "0019" "001A" "001B" "002F"
4162 "0030" "0031" "0032" "0033"
4163 "0034" "0035" "0036" "0037"
4164 "0038" "0039" "003A"
4165 "01" /* Compression Length : 0x01 = 1 byte for types */
4166 "00" /* Compression Type : 0x00 = NULL compression */
4167 };
4168
4169 struct tcpcheck_ruleset *rs = NULL;
4170 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4171 struct tcpcheck_rule *chk;
4172 char *errmsg = NULL;
4173 int err_code = 0;
4174
4175 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4176 err_code |= ERR_WARN;
4177
4178 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4179 goto out;
4180
4181 curpx->options2 &= ~PR_O2_CHK_ANY;
4182 curpx->options2 |= PR_O2_TCPCHK_CHK;
4183
4184 free_tcpcheck_vars(&rules->preset_vars);
4185 rules->list = NULL;
4186 rules->flags = 0;
4187
4188 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4189 if (rs)
4190 goto ruleset_found;
4191
4192 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4193 if (rs == NULL) {
4194 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4195 goto error;
4196 }
4197
4198 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4199 1, curpx, &rs->rules, file, line, &errmsg);
4200 if (!chk) {
4201 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4202 goto error;
4203 }
4204 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004205 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004206
4207 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4208 "min-recv", "5", "ok-status", "L6OK",
4209 "error-status", "L6RSP", "tout-status", "L6TOUT",
4210 ""},
4211 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4212 if (!chk) {
4213 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4214 goto error;
4215 }
4216 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004217 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004218
4219 ruleset_found:
4220 rules->list = &rs->rules;
4221 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4222 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4223
4224 out:
4225 free(errmsg);
4226 return err_code;
4227
4228 error:
4229 free_tcpcheck_ruleset(rs);
4230 err_code |= ERR_ALERT | ERR_FATAL;
4231 goto out;
4232}
4233
4234/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004235int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004236 const char *file, int line)
4237{
4238 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4239
4240 struct tcpcheck_ruleset *rs = NULL;
4241 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4242 struct tcpcheck_rule *chk;
4243 struct tcpcheck_var *var = NULL;
4244 char *cmd = NULL, *errmsg = NULL;
4245 int err_code = 0;
4246
4247 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4248 err_code |= ERR_WARN;
4249
4250 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4251 goto out;
4252
4253 curpx->options2 &= ~PR_O2_CHK_ANY;
4254 curpx->options2 |= PR_O2_TCPCHK_CHK;
4255
4256 free_tcpcheck_vars(&rules->preset_vars);
4257 rules->list = NULL;
4258 rules->flags = 0;
4259
4260 cur_arg += 2;
4261 if (*args[cur_arg] && *args[cur_arg+1] &&
4262 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4263 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4264 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4265 if (cmd)
4266 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4267 }
4268 else {
4269 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4270 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4271 cmd = strdup("HELO localhost");
4272 }
4273
4274 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4275 if (cmd == NULL || var == NULL) {
4276 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4277 goto error;
4278 }
4279 var->data.type = SMP_T_STR;
4280 var->data.u.str.area = cmd;
4281 var->data.u.str.data = strlen(cmd);
4282 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004283 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004284 cmd = NULL;
4285 var = NULL;
4286
4287 rs = find_tcpcheck_ruleset("*smtp-check");
4288 if (rs)
4289 goto ruleset_found;
4290
4291 rs = create_tcpcheck_ruleset("*smtp-check");
4292 if (rs == NULL) {
4293 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4294 goto error;
4295 }
4296
4297 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4298 1, curpx, &rs->rules, file, line, &errmsg);
4299 if (!chk) {
4300 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4301 goto error;
4302 }
4303 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004304 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004305
4306 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4307 "min-recv", "4",
4308 "error-status", "L7RSP",
4309 "on-error", "%[res.payload(0,0),cut_crlf]",
4310 ""},
4311 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4312 if (!chk) {
4313 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4314 goto error;
4315 }
4316 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004317 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004318
4319 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4320 "min-recv", "4",
4321 "error-status", "L7STS",
4322 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4323 "status-code", "res.payload(0,3)",
4324 ""},
4325 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4326 if (!chk) {
4327 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4328 goto error;
4329 }
4330 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004331 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004332
4333 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4334 1, curpx, &rs->rules, file, line, &errmsg);
4335 if (!chk) {
4336 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4337 goto error;
4338 }
4339 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004340 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004341
4342 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4343 "min-recv", "4",
4344 "error-status", "L7STS",
4345 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4346 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4347 "status-code", "res.payload(0,3)",
4348 ""},
4349 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4350 if (!chk) {
4351 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4352 goto error;
4353 }
4354 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004355 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004356
4357 ruleset_found:
4358 rules->list = &rs->rules;
4359 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4360 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4361
4362 out:
4363 free(errmsg);
4364 return err_code;
4365
4366 error:
4367 free(cmd);
4368 free(var);
4369 free_tcpcheck_vars(&rules->preset_vars);
4370 free_tcpcheck_ruleset(rs);
4371 err_code |= ERR_ALERT | ERR_FATAL;
4372 goto out;
4373}
4374
4375/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004376int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004377 const char *file, int line)
4378{
4379 static char pgsql_req[] = {
4380 "%[var(check.plen),htonl,hex]" /* The packet length*/
4381 "00030000" /* the version 3.0 */
4382 "7573657200" /* "user" key */
4383 "%[var(check.username),hex]00" /* the username */
4384 "00"
4385 };
4386
4387 struct tcpcheck_ruleset *rs = NULL;
4388 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4389 struct tcpcheck_rule *chk;
4390 struct tcpcheck_var *var = NULL;
4391 char *user = NULL, *errmsg = NULL;
4392 size_t packetlen = 0;
4393 int err_code = 0;
4394
4395 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4396 err_code |= ERR_WARN;
4397
4398 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4399 goto out;
4400
4401 curpx->options2 &= ~PR_O2_CHK_ANY;
4402 curpx->options2 |= PR_O2_TCPCHK_CHK;
4403
4404 free_tcpcheck_vars(&rules->preset_vars);
4405 rules->list = NULL;
4406 rules->flags = 0;
4407
4408 cur_arg += 2;
4409 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4410 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4411 file, line, args[0], args[1]);
4412 goto error;
4413 }
4414 if (strcmp(args[cur_arg], "user") == 0) {
4415 packetlen = 15 + strlen(args[cur_arg+1]);
4416 user = strdup(args[cur_arg+1]);
4417
4418 var = create_tcpcheck_var(ist("check.username"));
4419 if (user == NULL || var == NULL) {
4420 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4421 goto error;
4422 }
4423 var->data.type = SMP_T_STR;
4424 var->data.u.str.area = user;
4425 var->data.u.str.data = strlen(user);
4426 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004427 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004428 user = NULL;
4429 var = NULL;
4430
4431 var = create_tcpcheck_var(ist("check.plen"));
4432 if (var == NULL) {
4433 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4434 goto error;
4435 }
4436 var->data.type = SMP_T_SINT;
4437 var->data.u.sint = packetlen;
4438 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004439 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004440 var = NULL;
4441 }
4442 else {
4443 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4444 file, line, args[0], args[1]);
4445 goto error;
4446 }
4447
4448 rs = find_tcpcheck_ruleset("*pgsql-check");
4449 if (rs)
4450 goto ruleset_found;
4451
4452 rs = create_tcpcheck_ruleset("*pgsql-check");
4453 if (rs == NULL) {
4454 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4455 goto error;
4456 }
4457
4458 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4459 1, curpx, &rs->rules, file, line, &errmsg);
4460 if (!chk) {
4461 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4462 goto error;
4463 }
4464 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004465 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004466
4467 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4468 1, curpx, &rs->rules, file, line, &errmsg);
4469 if (!chk) {
4470 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4471 goto error;
4472 }
4473 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004474 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004475
4476 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4477 "min-recv", "5",
4478 "error-status", "L7RSP",
4479 "on-error", "%[res.payload(6,0)]",
4480 ""},
4481 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4482 if (!chk) {
4483 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4484 goto error;
4485 }
4486 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004487 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004488
4489 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4490 "min-recv", "9",
4491 "error-status", "L7STS",
4492 "on-success", "PostgreSQL server is ok",
4493 "on-error", "PostgreSQL unknown error",
4494 ""},
4495 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4496 if (!chk) {
4497 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4498 goto error;
4499 }
4500 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004501 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004502
4503 ruleset_found:
4504 rules->list = &rs->rules;
4505 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4506 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4507
4508 out:
4509 free(errmsg);
4510 return err_code;
4511
4512 error:
4513 free(user);
4514 free(var);
4515 free_tcpcheck_vars(&rules->preset_vars);
4516 free_tcpcheck_ruleset(rs);
4517 err_code |= ERR_ALERT | ERR_FATAL;
4518 goto out;
4519}
4520
4521
4522/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004523int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004524 const char *file, int line)
4525{
4526 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4527 * const char mysql40_client_auth_pkt[] = {
4528 * "\x0e\x00\x00" // packet length
4529 * "\x01" // packet number
4530 * "\x00\x00" // client capabilities
4531 * "\x00\x00\x01" // max packet
4532 * "haproxy\x00" // username (null terminated string)
4533 * "\x00" // filler (always 0x00)
4534 * "\x01\x00\x00" // packet length
4535 * "\x00" // packet number
4536 * "\x01" // COM_QUIT command
4537 * };
4538 */
4539 static char mysql40_rsname[] = "*mysql40-check";
4540 static char mysql40_req[] = {
4541 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4542 "0080" /* client capabilities */
4543 "000001" /* max packet */
4544 "%[var(check.username),hex]00" /* the username */
4545 "00" /* filler (always 0x00) */
4546 "010000" /* packet length*/
4547 "00" /* sequence ID */
4548 "01" /* COM_QUIT command */
4549 };
4550
4551 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4552 * const char mysql41_client_auth_pkt[] = {
4553 * "\x0e\x00\x00\" // packet length
4554 * "\x01" // packet number
4555 * "\x00\x00\x00\x00" // client capabilities
4556 * "\x00\x00\x00\x01" // max packet
4557 * "\x21" // character set (UTF-8)
4558 * char[23] // All zeroes
4559 * "haproxy\x00" // username (null terminated string)
4560 * "\x00" // filler (always 0x00)
4561 * "\x01\x00\x00" // packet length
4562 * "\x00" // packet number
4563 * "\x01" // COM_QUIT command
4564 * };
4565 */
4566 static char mysql41_rsname[] = "*mysql41-check";
4567 static char mysql41_req[] = {
4568 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4569 "00820000" /* client capabilities */
4570 "00800001" /* max packet */
4571 "21" /* character set (UTF-8) */
4572 "000000000000000000000000" /* 23 bytes, al zeroes */
4573 "0000000000000000000000"
4574 "%[var(check.username),hex]00" /* the username */
4575 "00" /* filler (always 0x00) */
4576 "010000" /* packet length*/
4577 "00" /* sequence ID */
4578 "01" /* COM_QUIT command */
4579 };
4580
4581 struct tcpcheck_ruleset *rs = NULL;
4582 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4583 struct tcpcheck_rule *chk;
4584 struct tcpcheck_var *var = NULL;
4585 char *mysql_rsname = "*mysql-check";
4586 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4587 int index = 0, err_code = 0;
4588
4589 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4590 err_code |= ERR_WARN;
4591
4592 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4593 goto out;
4594
4595 curpx->options2 &= ~PR_O2_CHK_ANY;
4596 curpx->options2 |= PR_O2_TCPCHK_CHK;
4597
4598 free_tcpcheck_vars(&rules->preset_vars);
4599 rules->list = NULL;
4600 rules->flags = 0;
4601
4602 cur_arg += 2;
4603 if (*args[cur_arg]) {
4604 int packetlen, userlen;
4605
4606 if (strcmp(args[cur_arg], "user") != 0) {
4607 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4608 file, line, args[0], args[1], args[cur_arg]);
4609 goto error;
4610 }
4611
4612 if (*(args[cur_arg+1]) == 0) {
4613 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4614 file, line, args[0], args[1], args[cur_arg]);
4615 goto error;
4616 }
4617
4618 hdr = calloc(4, sizeof(*hdr));
4619 user = strdup(args[cur_arg+1]);
4620 userlen = strlen(args[cur_arg+1]);
4621
4622 if (hdr == NULL || user == NULL) {
4623 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4624 goto error;
4625 }
4626
4627 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4628 packetlen = userlen + 7 + 27;
4629 mysql_req = mysql41_req;
4630 mysql_rsname = mysql41_rsname;
4631 }
4632 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4633 packetlen = userlen + 7;
4634 mysql_req = mysql40_req;
4635 mysql_rsname = mysql40_rsname;
4636 }
4637 else {
4638 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4639 file, line, args[cur_arg], args[cur_arg+2]);
4640 goto error;
4641 }
4642
4643 hdr[0] = (unsigned char)(packetlen & 0xff);
4644 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4645 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4646 hdr[3] = 1;
4647
4648 var = create_tcpcheck_var(ist("check.header"));
4649 if (var == NULL) {
4650 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4651 goto error;
4652 }
4653 var->data.type = SMP_T_STR;
4654 var->data.u.str.area = hdr;
4655 var->data.u.str.data = 4;
4656 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004657 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004658 hdr = NULL;
4659 var = NULL;
4660
4661 var = create_tcpcheck_var(ist("check.username"));
4662 if (var == NULL) {
4663 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4664 goto error;
4665 }
4666 var->data.type = SMP_T_STR;
4667 var->data.u.str.area = user;
4668 var->data.u.str.data = strlen(user);
4669 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004670 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004671 user = NULL;
4672 var = NULL;
4673 }
4674
4675 rs = find_tcpcheck_ruleset(mysql_rsname);
4676 if (rs)
4677 goto ruleset_found;
4678
4679 rs = create_tcpcheck_ruleset(mysql_rsname);
4680 if (rs == NULL) {
4681 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4682 goto error;
4683 }
4684
4685 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4686 1, curpx, &rs->rules, file, line, &errmsg);
4687 if (!chk) {
4688 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4689 goto error;
4690 }
4691 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004692 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004693
4694 if (mysql_req) {
4695 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4696 1, curpx, &rs->rules, file, line, &errmsg);
4697 if (!chk) {
4698 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4699 goto error;
4700 }
4701 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004702 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004703 }
4704
4705 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4706 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4707 if (!chk) {
4708 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4709 goto error;
4710 }
4711 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4712 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004713 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004714
4715 if (mysql_req) {
4716 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4717 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4718 if (!chk) {
4719 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4720 goto error;
4721 }
4722 chk->expect.custom = tcpcheck_mysql_expect_ok;
4723 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004724 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004725 }
4726
4727 ruleset_found:
4728 rules->list = &rs->rules;
4729 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4730 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4731
4732 out:
4733 free(errmsg);
4734 return err_code;
4735
4736 error:
4737 free(hdr);
4738 free(user);
4739 free(var);
4740 free_tcpcheck_vars(&rules->preset_vars);
4741 free_tcpcheck_ruleset(rs);
4742 err_code |= ERR_ALERT | ERR_FATAL;
4743 goto out;
4744}
4745
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004746int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004747 const char *file, int line)
4748{
4749 static char *ldap_req = "300C020101600702010304008000";
4750
4751 struct tcpcheck_ruleset *rs = NULL;
4752 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4753 struct tcpcheck_rule *chk;
4754 char *errmsg = NULL;
4755 int err_code = 0;
4756
4757 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4758 err_code |= ERR_WARN;
4759
4760 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4761 goto out;
4762
4763 curpx->options2 &= ~PR_O2_CHK_ANY;
4764 curpx->options2 |= PR_O2_TCPCHK_CHK;
4765
4766 free_tcpcheck_vars(&rules->preset_vars);
4767 rules->list = NULL;
4768 rules->flags = 0;
4769
4770 rs = find_tcpcheck_ruleset("*ldap-check");
4771 if (rs)
4772 goto ruleset_found;
4773
4774 rs = create_tcpcheck_ruleset("*ldap-check");
4775 if (rs == NULL) {
4776 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4777 goto error;
4778 }
4779
4780 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4781 1, curpx, &rs->rules, file, line, &errmsg);
4782 if (!chk) {
4783 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4784 goto error;
4785 }
4786 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004787 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004788
4789 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4790 "min-recv", "14",
4791 "on-error", "Not LDAPv3 protocol",
4792 ""},
4793 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4794 if (!chk) {
4795 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4796 goto error;
4797 }
4798 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004799 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004800
4801 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4802 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4803 if (!chk) {
4804 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4805 goto error;
4806 }
4807 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4808 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004809 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004810
4811 ruleset_found:
4812 rules->list = &rs->rules;
4813 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4814 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4815
4816 out:
4817 free(errmsg);
4818 return err_code;
4819
4820 error:
4821 free_tcpcheck_ruleset(rs);
4822 err_code |= ERR_ALERT | ERR_FATAL;
4823 goto out;
4824}
4825
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004826int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004827 const char *file, int line)
4828{
4829 struct tcpcheck_ruleset *rs = NULL;
4830 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4831 struct tcpcheck_rule *chk;
4832 char *spop_req = NULL;
4833 char *errmsg = NULL;
4834 int spop_len = 0, err_code = 0;
4835
4836 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4837 err_code |= ERR_WARN;
4838
4839 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4840 goto out;
4841
4842 curpx->options2 &= ~PR_O2_CHK_ANY;
4843 curpx->options2 |= PR_O2_TCPCHK_CHK;
4844
4845 free_tcpcheck_vars(&rules->preset_vars);
4846 rules->list = NULL;
4847 rules->flags = 0;
4848
4849
4850 rs = find_tcpcheck_ruleset("*spop-check");
4851 if (rs)
4852 goto ruleset_found;
4853
4854 rs = create_tcpcheck_ruleset("*spop-check");
4855 if (rs == NULL) {
4856 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4857 goto error;
4858 }
4859
4860 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4861 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4862 goto error;
4863 }
4864 chunk_reset(&trash);
4865 dump_binary(&trash, spop_req, spop_len);
4866 trash.area[trash.data] = '\0';
4867
4868 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4869 1, curpx, &rs->rules, file, line, &errmsg);
4870 if (!chk) {
4871 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4872 goto error;
4873 }
4874 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004875 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004876
4877 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4878 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4879 if (!chk) {
4880 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4881 goto error;
4882 }
4883 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4884 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004885 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004886
4887 ruleset_found:
4888 rules->list = &rs->rules;
4889 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4890 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4891
4892 out:
4893 free(spop_req);
4894 free(errmsg);
4895 return err_code;
4896
4897 error:
4898 free_tcpcheck_ruleset(rs);
4899 err_code |= ERR_ALERT | ERR_FATAL;
4900 goto out;
4901}
4902
4903
4904static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4905{
4906 struct tcpcheck_rule *chk = NULL;
4907 struct tcpcheck_http_hdr *hdr = NULL;
4908 char *meth = NULL, *uri = NULL, *vsn = NULL;
4909 char *hdrs, *body;
4910
4911 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4912 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4913 if (hdrs == body)
4914 hdrs = NULL;
4915 if (hdrs) {
4916 *hdrs = '\0';
4917 hdrs +=2;
4918 }
4919 if (body) {
4920 *body = '\0';
4921 body += 4;
4922 }
4923 if (hdrs || body) {
4924 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4925 " Please, consider to use 'http-check send' directive instead.");
4926 }
4927
4928 chk = calloc(1, sizeof(*chk));
4929 if (!chk) {
4930 memprintf(errmsg, "out of memory");
4931 goto error;
4932 }
4933 chk->action = TCPCHK_ACT_SEND;
4934 chk->send.type = TCPCHK_SEND_HTTP;
4935 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4936 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4937 LIST_INIT(&chk->send.http.hdrs);
4938
4939 /* Copy the method, uri and version */
4940 if (*args[cur_arg]) {
4941 if (!*args[cur_arg+1])
4942 uri = args[cur_arg];
4943 else
4944 meth = args[cur_arg];
4945 }
4946 if (*args[cur_arg+1])
4947 uri = args[cur_arg+1];
4948 if (*args[cur_arg+2])
4949 vsn = args[cur_arg+2];
4950
4951 if (meth) {
4952 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4953 chk->send.http.meth.str.area = strdup(meth);
4954 chk->send.http.meth.str.data = strlen(meth);
4955 if (!chk->send.http.meth.str.area) {
4956 memprintf(errmsg, "out of memory");
4957 goto error;
4958 }
4959 }
4960 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004961 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004962 if (!isttest(chk->send.http.uri)) {
4963 memprintf(errmsg, "out of memory");
4964 goto error;
4965 }
4966 }
4967 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004968 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004969 if (!isttest(chk->send.http.vsn)) {
4970 memprintf(errmsg, "out of memory");
4971 goto error;
4972 }
4973 }
4974
4975 /* Copy the header */
4976 if (hdrs) {
4977 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4978 struct h1m h1m;
4979 int i, ret;
4980
4981 /* Build and parse the request */
4982 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4983
4984 h1m.flags = H1_MF_HDRS_ONLY;
4985 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4986 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4987 &h1m, NULL);
4988 if (ret <= 0) {
4989 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4990 goto error;
4991 }
4992
4993 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4994 hdr = calloc(1, sizeof(*hdr));
4995 if (!hdr) {
4996 memprintf(errmsg, "out of memory");
4997 goto error;
4998 }
4999 LIST_INIT(&hdr->value);
5000 hdr->name = istdup(tmp_hdrs[i].n);
5001 if (!hdr->name.ptr) {
5002 memprintf(errmsg, "out of memory");
5003 goto error;
5004 }
5005
5006 ist0(tmp_hdrs[i].v);
5007 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5008 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005009 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005010 }
5011 }
5012
5013 /* Copy the body */
5014 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005015 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005016 if (!isttest(chk->send.http.body)) {
5017 memprintf(errmsg, "out of memory");
5018 goto error;
5019 }
5020 }
5021
5022 return chk;
5023
5024 error:
5025 free_tcpcheck_http_hdr(hdr);
5026 free_tcpcheck(chk, 0);
5027 return NULL;
5028}
5029
5030/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005031int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005032 const char *file, int line)
5033{
5034 struct tcpcheck_ruleset *rs = NULL;
5035 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5036 struct tcpcheck_rule *chk;
5037 char *errmsg = NULL;
5038 int err_code = 0;
5039
5040 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5041 err_code |= ERR_WARN;
5042
5043 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5044 goto out;
5045
5046 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5047 if (!chk) {
5048 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5049 goto error;
5050 }
5051 if (errmsg) {
5052 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5053 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005054 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005055 }
5056
5057 no_request:
5058 curpx->options2 &= ~PR_O2_CHK_ANY;
5059 curpx->options2 |= PR_O2_TCPCHK_CHK;
5060
5061 free_tcpcheck_vars(&rules->preset_vars);
5062 rules->list = NULL;
5063 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5064
5065 /* Deduce the ruleset name from the proxy info */
5066 chunk_printf(&trash, "*http-check-%s_%s-%d",
5067 ((curpx == defpx) ? "defaults" : curpx->id),
5068 curpx->conf.file, curpx->conf.line);
5069
5070 rs = find_tcpcheck_ruleset(b_orig(&trash));
5071 if (rs == NULL) {
5072 rs = create_tcpcheck_ruleset(b_orig(&trash));
5073 if (rs == NULL) {
5074 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5075 goto error;
5076 }
5077 }
5078
5079 rules->list = &rs->rules;
5080 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5081 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5082 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5083 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5084 rules->list = NULL;
5085 goto error;
5086 }
5087
5088 out:
5089 free(errmsg);
5090 return err_code;
5091
5092 error:
5093 free_tcpcheck_ruleset(rs);
5094 free_tcpcheck(chk, 0);
5095 err_code |= ERR_ALERT | ERR_FATAL;
5096 goto out;
5097}
5098
5099/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005100int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005101 const char *file, int line)
5102{
5103 struct tcpcheck_ruleset *rs = NULL;
5104 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5105 int err_code = 0;
5106
5107 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5108 err_code |= ERR_WARN;
5109
5110 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5111 goto out;
5112
5113 curpx->options2 &= ~PR_O2_CHK_ANY;
5114 curpx->options2 |= PR_O2_TCPCHK_CHK;
5115
5116 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5117 /* If a tcp-check rulesset is already set, do nothing */
5118 if (rules->list)
5119 goto out;
5120
5121 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5122 * get it.
5123 */
5124 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5125 goto curpx_ruleset;
5126
5127 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5128 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5129 rs = find_tcpcheck_ruleset(b_orig(&trash));
5130 if (rs)
5131 goto ruleset_found;
5132 }
5133
5134 curpx_ruleset:
5135 /* Deduce the ruleset name from the proxy info */
5136 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5137 ((curpx == defpx) ? "defaults" : curpx->id),
5138 curpx->conf.file, curpx->conf.line);
5139
5140 rs = find_tcpcheck_ruleset(b_orig(&trash));
5141 if (rs == NULL) {
5142 rs = create_tcpcheck_ruleset(b_orig(&trash));
5143 if (rs == NULL) {
5144 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5145 goto error;
5146 }
5147 }
5148
5149 ruleset_found:
5150 free_tcpcheck_vars(&rules->preset_vars);
5151 rules->list = &rs->rules;
5152 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5153 rules->flags |= TCPCHK_RULES_TCP_CHK;
5154
5155 out:
5156 return err_code;
5157
5158 error:
5159 err_code |= ERR_ALERT | ERR_FATAL;
5160 goto out;
5161}
5162
Willy Tarreau51cd5952020-06-05 12:25:38 +02005163static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005164 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005165 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5166 { 0, NULL, NULL },
5167}};
5168
5169REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5170REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5171REGISTER_POST_DEINIT(deinit_tcpchecks);
5172INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);