blob: 0ac825e0601296e90e5947962f4955c3421cbe1f [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 Faulet54e85cb2022-01-06 08:46:56 +01001060 struct connection *conn = cs_conn(check->cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001061 struct protocol *proto;
1062 struct xprt_ops *xprt;
1063 struct tcpcheck_rule *next;
1064 int status, port;
1065
Christopher Faulet147b8c92021-04-10 09:00:38 +02001066 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1067
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001068 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1069
1070 /* current connection already created, check if it is established or not */
1071 if (conn) {
1072 if (conn->flags & CO_FL_WAIT_XPRT) {
1073 /* We are still waiting for the connection establishment */
1074 if (next && next->action == TCPCHK_ACT_SEND) {
1075 if (!(check->wait_list.events & SUB_RETRY_SEND))
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001076 conn->mux->subscribe(check->cs, SUB_RETRY_SEND, &check->wait_list);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001077 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001078 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001079 }
1080 else
1081 ret = tcpcheck_eval_recv(check, rule);
1082 }
1083 goto out;
1084 }
1085
1086 /* Note: here check->cs = cs = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001087
Christopher Fauletb381a502020-11-25 13:47:00 +01001088 /* Always release input and output buffer when a new connect is evaluated */
1089 check_release_buf(check, &check->bi);
1090 check_release_buf(check, &check->bo);
1091
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001092 /* No connection, prepare a new one */
Christopher Fauletdd2d0d82021-12-20 09:34:32 +01001093 conn = conn_new((s ? &s->obj_type : &proxy->obj_type));
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001094 if (!conn) {
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001095 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1096 tcpcheck_get_step_id(check, rule));
1097 if (rule->comment)
1098 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1099 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1100 ret = TCPCHK_EVAL_STOP;
1101 TRACE_ERROR("conn-stream allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001102 goto out;
1103 }
Christopher Faulet070b91b2022-03-31 19:27:18 +02001104 if (cs_attach_mux(check->cs, NULL, conn) < 0) {
1105 TRACE_ERROR("mux attach error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
1106 conn_free(conn);
1107 conn = NULL;
1108 status = SF_ERR_RESOURCE;
1109 goto fail_check;
1110 }
Christopher Fauleta9e8b392022-03-23 11:01:09 +01001111 conn->ctx = check->cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001112 tasklet_set_tid(check->wait_list.tasklet, tid);
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
Christopher Fauletf7177272020-10-02 13:41:55 +02001169 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1170 conn->send_proxy_ofs = 1;
1171 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001172 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001173 }
1174 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (s->flags & SRV_F_SOCKS4_PROXY)) {
1175 conn->send_proxy_ofs = 1;
1176 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001177 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001178 }
1179
1180 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1181 conn->send_proxy_ofs = 1;
1182 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001183 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001184 }
1185 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
1186 conn->send_proxy_ofs = 1;
1187 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001188 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001189 }
1190
Willy Tarreau51cd5952020-06-05 12:25:38 +02001191 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001192 if (proto && proto->connect) {
1193 int flags = 0;
1194
1195 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1196 flags |= CONNECT_HAS_DATA;
1197 if (!next || next->action != TCPCHK_ACT_EXPECT)
1198 flags |= CONNECT_DELACK_ALWAYS;
1199 status = proto->connect(conn, flags);
1200 }
1201
1202 if (status != SF_ERR_NONE)
1203 goto fail_check;
1204
Christopher Faulet21ddc742020-07-01 15:26:14 +02001205 conn_set_private(conn);
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001206 conn->ctx = check->cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001207
Willy Tarreau51cd5952020-06-05 12:25:38 +02001208#ifdef USE_OPENSSL
1209 if (connect->sni)
1210 ssl_sock_set_servername(conn, connect->sni);
1211 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1212 ssl_sock_set_servername(conn, s->check.sni);
1213
1214 if (connect->alpn)
1215 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1216 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1217 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1218#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001219
Willy Tarreaue2226792022-04-11 18:04:33 +02001220 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER) && !(conn->flags & CO_FL_FDLESS)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001221 /* Some servers don't like reset on close */
Christopher Faulet897d6122021-12-17 17:28:35 +01001222 HA_ATOMIC_AND(&fdtab[conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001223 }
1224
1225 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1226 if (xprt_add_hs(conn) < 0)
1227 status = SF_ERR_RESOURCE;
1228 }
1229
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001230 if (conn_xprt_start(conn) < 0) {
1231 status = SF_ERR_RESOURCE;
1232 goto fail_check;
1233 }
1234
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001235 /* The mux may be initialized now if there isn't server attached to the
1236 * check (email alerts) or if there is a mux proto specified or if there
1237 * is no alpn.
1238 */
1239 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1240 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1241 const struct mux_ops *mux_ops;
1242
Christopher Faulet147b8c92021-04-10 09:00:38 +02001243 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001244 if (connect->mux_proto)
1245 mux_ops = connect->mux_proto->mux;
1246 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1247 mux_ops = check->mux_proto->mux;
1248 else {
1249 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1250 ? PROTO_MODE_HTTP
1251 : PROTO_MODE_TCP);
1252
1253 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1254 }
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001255 if (mux_ops && conn_install_mux(conn, mux_ops, check->cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001256 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001257 status = SF_ERR_INTERNAL;
1258 goto fail_check;
1259 }
1260 }
1261
Willy Tarreau51cd5952020-06-05 12:25:38 +02001262 fail_check:
1263 /* It can return one of :
1264 * - SF_ERR_NONE if everything's OK
1265 * - SF_ERR_SRVTO if there are no more servers
1266 * - SF_ERR_SRVCL if the connection was refused by the server
1267 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1268 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1269 * - SF_ERR_INTERNAL for any other purely internal errors
1270 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1271 * Note that we try to prevent the network stack from sending the ACK during the
1272 * connect() when a pure TCP check is used (without PROXY protocol).
1273 */
1274 switch (status) {
1275 case SF_ERR_NONE:
1276 /* we allow up to min(inter, timeout.connect) for a connection
1277 * to establish but only when timeout.check is set as it may be
1278 * to short for a full check otherwise
1279 */
1280 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1281
1282 if (proxy->timeout.check && proxy->timeout.connect) {
1283 int t_con = tick_add(now_ms, proxy->timeout.connect);
1284 t->expire = tick_first(t->expire, t_con);
1285 }
1286 break;
1287 case SF_ERR_SRVTO: /* ETIMEDOUT */
1288 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1289 case SF_ERR_PRXCOND:
1290 case SF_ERR_RESOURCE:
1291 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001292 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 +02001293 chk_report_conn_err(check, errno, 0);
1294 ret = TCPCHK_EVAL_STOP;
1295 goto out;
1296 }
1297
1298 /* don't do anything until the connection is established */
1299 if (conn->flags & CO_FL_WAIT_XPRT) {
1300 if (conn->mux) {
1301 if (next && next->action == TCPCHK_ACT_SEND)
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001302 conn->mux->subscribe(check->cs, SUB_RETRY_SEND, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001303 else
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001304 conn->mux->subscribe(check->cs, SUB_RETRY_RECV, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001305 }
1306 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001307 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001308 goto out;
1309 }
1310
1311 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001312 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001313 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001314 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001315 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001316
1317 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1318 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1319
Christopher Faulet147b8c92021-04-10 09:00:38 +02001320 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001321 return ret;
1322}
1323
1324/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1325 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1326 * TCPCHK_EVAL_STOP if an error occurred.
1327 */
1328enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1329{
1330 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1331 struct tcpcheck_send *send = &rule->send;
1332 struct conn_stream *cs = check->cs;
Christopher Faulet693b23b2022-02-28 09:09:05 +01001333 struct connection *conn = __cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001334 struct buffer *tmp = NULL;
1335 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001336 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001337
Christopher Faulet147b8c92021-04-10 09:00:38 +02001338 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1339
Christopher Fauletb381a502020-11-25 13:47:00 +01001340 if (check->state & CHK_ST_OUT_ALLOC) {
1341 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001342 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 +01001343 goto out;
1344 }
1345
1346 if (!check_get_buf(check, &check->bo)) {
1347 check->state |= CHK_ST_OUT_ALLOC;
1348 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001349 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 +01001350 goto out;
1351 }
1352
Christopher Faulet39066c22020-11-25 13:34:51 +01001353 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001354 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 +02001355 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 +01001356 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001357 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001358
Christopher Fauletb381a502020-11-25 13:47:00 +01001359 /* Always release input buffer when a new send is evaluated */
1360 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001361
1362 switch (send->type) {
1363 case TCPCHK_SEND_STRING:
1364 case TCPCHK_SEND_BINARY:
1365 if (istlen(send->data) >= b_size(&check->bo)) {
1366 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1367 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1368 tcpcheck_get_step_id(check, rule));
1369 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1370 ret = TCPCHK_EVAL_STOP;
1371 goto out;
1372 }
1373 b_putist(&check->bo, send->data);
1374 break;
1375 case TCPCHK_SEND_STRING_LF:
1376 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1377 if (!b_data(&check->bo))
1378 goto out;
1379 break;
1380 case TCPCHK_SEND_BINARY_LF: {
1381 int len = b_size(&check->bo);
1382
1383 tmp = alloc_trash_chunk();
1384 if (!tmp)
1385 goto error_lf;
1386 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1387 if (!b_data(tmp))
1388 goto out;
1389 tmp->area[tmp->data] = '\0';
1390 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1391 goto error_lf;
1392 check->bo.data = len;
1393 break;
1394 }
1395 case TCPCHK_SEND_HTTP: {
1396 struct htx_sl *sl;
1397 struct ist meth, uri, vsn, clen, body;
1398 unsigned int slflags = 0;
1399
1400 tmp = alloc_trash_chunk();
1401 if (!tmp)
1402 goto error_htx;
1403
1404 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1405 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1406 : http_known_methods[send->http.meth.meth]);
1407 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1408 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1409 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1410 }
1411 else
1412 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1413 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1414
1415 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1416 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1417 slflags |= HTX_SL_F_VER_11;
1418 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1419 if (!isttest(send->http.body))
1420 slflags |= HTX_SL_F_BODYLESS;
1421
1422 htx = htx_from_buf(&check->bo);
1423 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1424 if (!sl)
1425 goto error_htx;
1426 sl->info.req.meth = send->http.meth.meth;
1427 if (!http_update_host(htx, sl, uri))
1428 goto error_htx;
1429
1430 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1431 struct tcpcheck_http_hdr *hdr;
1432 struct ist hdr_value;
1433
1434 list_for_each_entry(hdr, &send->http.hdrs, list) {
1435 chunk_reset(tmp);
1436 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1437 if (!b_data(tmp))
1438 continue;
1439 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1440 if (!htx_add_header(htx, hdr->name, hdr_value))
1441 goto error_htx;
1442 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1443 if (!http_update_authority(htx, sl, hdr_value))
1444 goto error_htx;
1445 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001446 if (isteqi(hdr->name, ist("connection")))
1447 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001448 }
1449
1450 }
1451 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1452 chunk_reset(tmp);
1453 httpchk_build_status_header(check->server, tmp);
1454 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1455 goto error_htx;
1456 }
1457
1458
1459 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1460 chunk_reset(tmp);
1461 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1462 body = ist2(b_orig(tmp), b_data(tmp));
1463 }
1464 else
1465 body = send->http.body;
1466 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1467
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001468 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001469 !htx_add_header(htx, ist("Content-length"), clen))
1470 goto error_htx;
1471
1472
1473 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001474 (istlen(body) && !htx_add_data_atonce(htx, body)))
1475 goto error_htx;
1476
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001477 /* no more data are expected */
1478 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001479 htx_to_buf(htx, &check->bo);
1480 break;
1481 }
1482 case TCPCHK_SEND_UNDEF:
1483 /* Should never happen. */
1484 ret = TCPCHK_EVAL_STOP;
1485 goto out;
1486 };
1487
Christopher Faulet39066c22020-11-25 13:34:51 +01001488 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001489 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001490 if (conn->mux->snd_buf(cs, &check->bo,
1491 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Christopher Fauletb041b232022-03-24 10:27:02 +01001492 if ((conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001493 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001494 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 +02001495 goto out;
1496 }
1497 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001498 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 +01001499 conn->mux->subscribe(cs, SUB_RETRY_SEND, &check->wait_list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001500 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001501 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001502 goto out;
1503 }
1504
1505 out:
1506 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001507 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1508 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001509
1510 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001511 return ret;
1512
1513 error_htx:
1514 if (htx) {
1515 htx_reset(htx);
1516 htx_to_buf(htx, &check->bo);
1517 }
1518 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1519 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001520 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 +02001521 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1522 ret = TCPCHK_EVAL_STOP;
1523 goto out;
1524
1525 error_lf:
1526 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1527 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001528 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 +02001529 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1530 ret = TCPCHK_EVAL_STOP;
1531 goto out;
1532
1533}
1534
1535/* Try to receive data before evaluating a tcp-check expect rule. Returns
1536 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1537 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1538 * TCPCHK_EVAL_STOP if an error occurred.
1539 */
1540enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1541{
1542 struct conn_stream *cs = check->cs;
Christopher Faulet693b23b2022-02-28 09:09:05 +01001543 struct connection *conn = __cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001544 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1545 size_t max, read, cur_read = 0;
1546 int is_empty;
1547 int read_poll = MAX_READ_POLL_LOOPS;
1548
Christopher Faulet147b8c92021-04-10 09:00:38 +02001549 TRACE_ENTER(CHK_EV_RX_DATA, check);
1550
1551 if (check->wait_list.events & SUB_RETRY_RECV) {
1552 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001553 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001554 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001555
Christopher Fauletb041b232022-03-24 10:27:02 +01001556 if (cs->endp->flags & CS_EP_EOS)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001557 goto end_recv;
1558
Christopher Faulet147b8c92021-04-10 09:00:38 +02001559 if (check->state & CHK_ST_IN_ALLOC) {
1560 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001561 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001562 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001563
1564 if (!check_get_buf(check, &check->bi)) {
1565 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001566 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001567 goto wait_more_data;
1568 }
1569
Willy Tarreau51cd5952020-06-05 12:25:38 +02001570 /* errors on the connection and the conn-stream were already checked */
1571
1572 /* prepare to detect if the mux needs more room */
Christopher Fauletb041b232022-03-24 10:27:02 +01001573 cs->endp->flags &= ~CS_EP_WANT_ROOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001574
Christopher Fauletb041b232022-03-24 10:27:02 +01001575 while ((cs->endp->flags & CS_EP_RCV_MORE) ||
1576 (!(conn->flags & CO_FL_ERROR) && !(cs->endp->flags & (CS_EP_ERROR|CS_EP_EOS)))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001577 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1578 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1579 cur_read += read;
1580 if (!read ||
Christopher Fauletb041b232022-03-24 10:27:02 +01001581 (cs->endp->flags & CS_EP_WANT_ROOM) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001582 (--read_poll <= 0) ||
1583 (read < max && read >= global.tune.recv_enough))
1584 break;
1585 }
1586
1587 end_recv:
1588 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Christopher Fauletb041b232022-03-24 10:27:02 +01001589 if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001590 /* Report network errors only if we got no other data. Otherwise
1591 * we'll let the upper layers decide whether the response is OK
1592 * or not. It is very common that an RST sent by the server is
1593 * reported as an error just after the last data chunk.
1594 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001595 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001596 goto stop;
1597 }
1598 if (!cur_read) {
Christopher Fauletb041b232022-03-24 10:27:02 +01001599 if (cs->endp->flags & CS_EP_EOI) {
Christopher Fauletd16e7dd2021-10-20 13:53:38 +02001600 /* If EOI is set, it means there is a response or an error */
1601 goto out;
1602 }
Christopher Fauletb041b232022-03-24 10:27:02 +01001603 if (!(cs->endp->flags & (CS_EP_WANT_ROOM|CS_EP_ERROR|CS_EP_EOS))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001604 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001605 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001606 goto wait_more_data;
1607 }
1608 if (is_empty) {
1609 int status;
1610
1611 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1612 tcpcheck_get_step_id(check, rule));
1613 if (rule->comment)
1614 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1615
Christopher Faulet147b8c92021-04-10 09:00:38 +02001616 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001617 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1618 set_server_check_status(check, status, trash.area);
1619 goto stop;
1620 }
1621 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001622 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001623
1624 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001625 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1626 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001627
1628 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001629 return ret;
1630
1631 stop:
1632 ret = TCPCHK_EVAL_STOP;
1633 goto out;
1634
1635 wait_more_data:
1636 ret = TCPCHK_EVAL_WAIT;
1637 goto out;
1638}
1639
1640/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1641 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1642 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1643 * error occurred.
1644 */
1645enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1646{
1647 struct htx *htx = htxbuf(&check->bi);
1648 struct htx_sl *sl;
1649 struct htx_blk *blk;
1650 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1651 struct tcpcheck_expect *expect = &rule->expect;
1652 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1653 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1654 struct ist desc = IST_NULL;
1655 int i, match, inverse;
1656
Christopher Faulet147b8c92021-04-10 09:00:38 +02001657 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1658
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001659 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001660
1661 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001662 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001663 status = HCHK_STATUS_L7RSP;
1664 goto error;
1665 }
1666
1667 if (htx_is_empty(htx)) {
1668 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001669 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001670 status = HCHK_STATUS_L7RSP;
1671 goto error;
1672 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001673 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001674 goto wait_more_data;
1675 }
1676
1677 sl = http_get_stline(htx);
1678 check->code = sl->info.res.status;
1679
1680 if (check->server &&
1681 (check->server->proxy->options & PR_O_DISABLE404) &&
1682 (check->server->next_state != SRV_ST_STOPPED) &&
1683 (check->code == 404)) {
1684 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001685 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001686 goto out;
1687 }
1688
1689 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1690 /* Make GCC happy ; initialize match to a failure state. */
1691 match = inverse;
1692 status = expect->err_status;
1693
1694 switch (expect->type) {
1695 case TCPCHK_EXPECT_HTTP_STATUS:
1696 match = 0;
1697 for (i = 0; i < expect->codes.num; i++) {
1698 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1699 sl->info.res.status <= expect->codes.codes[i][1]) {
1700 match = 1;
1701 break;
1702 }
1703 }
1704
1705 /* Set status and description in case of error */
1706 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1707 if (LIST_ISEMPTY(&expect->onerror_fmt))
1708 desc = htx_sl_res_reason(sl);
1709 break;
1710 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1711 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1712
1713 /* Set status and description in case of error */
1714 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1715 if (LIST_ISEMPTY(&expect->onerror_fmt))
1716 desc = htx_sl_res_reason(sl);
1717 break;
1718
1719 case TCPCHK_EXPECT_HTTP_HEADER: {
1720 struct http_hdr_ctx ctx;
1721 struct ist npat, vpat, value;
1722 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1723
1724 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1725 nbuf = alloc_trash_chunk();
1726 if (!nbuf) {
1727 status = HCHK_STATUS_L7RSP;
1728 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001729 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001730 goto error;
1731 }
1732 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1733 if (!b_data(nbuf)) {
1734 status = HCHK_STATUS_L7RSP;
1735 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001736 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001737 goto error;
1738 }
1739 npat = ist2(b_orig(nbuf), b_data(nbuf));
1740 }
1741 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1742 npat = expect->hdr.name;
1743
1744 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1745 vbuf = alloc_trash_chunk();
1746 if (!vbuf) {
1747 status = HCHK_STATUS_L7RSP;
1748 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001749 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001750 goto error;
1751 }
1752 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1753 if (!b_data(vbuf)) {
1754 status = HCHK_STATUS_L7RSP;
1755 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001756 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001757 goto error;
1758 }
1759 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1760 }
1761 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1762 vpat = expect->hdr.value;
1763
1764 match = 0;
1765 ctx.blk = NULL;
1766 while (1) {
1767 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1768 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1769 if (!http_find_str_header(htx, npat, &ctx, full))
1770 goto end_of_match;
1771 break;
1772 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1773 if (!http_find_pfx_header(htx, npat, &ctx, full))
1774 goto end_of_match;
1775 break;
1776 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1777 if (!http_find_sfx_header(htx, npat, &ctx, full))
1778 goto end_of_match;
1779 break;
1780 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1781 if (!http_find_sub_header(htx, npat, &ctx, full))
1782 goto end_of_match;
1783 break;
1784 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1785 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1786 goto end_of_match;
1787 break;
1788 default:
1789 /* should never happen */
1790 goto end_of_match;
1791 }
1792
1793 /* A header has matched the name pattern, let's test its
1794 * value now (always defined from there). If there is no
1795 * value pattern, it is a good match.
1796 */
1797
1798 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1799 match = 1;
1800 goto end_of_match;
1801 }
1802
1803 value = ctx.value;
1804 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1805 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1806 if (isteq(value, vpat)) {
1807 match = 1;
1808 goto end_of_match;
1809 }
1810 break;
1811 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1812 if (istlen(value) < istlen(vpat))
1813 break;
1814 value = ist2(istptr(value), istlen(vpat));
1815 if (isteq(value, vpat)) {
1816 match = 1;
1817 goto end_of_match;
1818 }
1819 break;
1820 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1821 if (istlen(value) < istlen(vpat))
1822 break;
Tim Duesterhus4c8f75f2021-11-06 15:14:44 +01001823 value = ist2(istend(value) - istlen(vpat), istlen(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001824 if (isteq(value, vpat)) {
1825 match = 1;
1826 goto end_of_match;
1827 }
1828 break;
1829 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1830 if (isttest(istist(value, vpat))) {
1831 match = 1;
1832 goto end_of_match;
1833 }
1834 break;
1835 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1836 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1837 match = 1;
1838 goto end_of_match;
1839 }
1840 break;
1841 }
1842 }
1843
1844 end_of_match:
1845 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1846 if (LIST_ISEMPTY(&expect->onerror_fmt))
1847 desc = htx_sl_res_reason(sl);
1848 break;
1849 }
1850
1851 case TCPCHK_EXPECT_HTTP_BODY:
1852 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1853 case TCPCHK_EXPECT_HTTP_BODY_LF:
1854 match = 0;
1855 chunk_reset(&trash);
1856 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1857 enum htx_blk_type type = htx_get_blk_type(blk);
1858
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001859 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001860 break;
1861 if (type == HTX_BLK_DATA) {
1862 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1863 break;
1864 }
1865 }
1866
1867 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001868 if (!last_read) {
1869 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001870 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001871 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001872 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1873 if (LIST_ISEMPTY(&expect->onerror_fmt))
1874 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001875 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001876 goto error;
1877 }
1878
1879 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1880 tmp = alloc_trash_chunk();
1881 if (!tmp) {
1882 status = HCHK_STATUS_L7RSP;
1883 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001884 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001885 goto error;
1886 }
1887 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1888 if (!b_data(tmp)) {
1889 status = HCHK_STATUS_L7RSP;
1890 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001891 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001892 goto error;
1893 }
1894 }
1895
1896 if (!last_read &&
1897 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1898 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1899 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1900 ret = TCPCHK_EVAL_WAIT;
1901 goto out;
1902 }
1903
1904 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1905 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1906 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1907 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1908 else
1909 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1910
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001911 /* Wait for more data on mismatch only if no minimum is defined (-1),
1912 * otherwise the absence of match is already conclusive.
1913 */
1914 if (!match && !last_read && (expect->min_recv == -1)) {
1915 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001916 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001917 goto out;
1918 }
1919
Willy Tarreau51cd5952020-06-05 12:25:38 +02001920 /* Set status and description in case of error */
1921 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1922 if (LIST_ISEMPTY(&expect->onerror_fmt))
1923 desc = (inverse
1924 ? ist("HTTP check matched unwanted content")
1925 : ist("HTTP content check did not match"));
1926 break;
1927
1928
1929 default:
1930 /* should never happen */
1931 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1932 goto error;
1933 }
1934
Christopher Faulet147b8c92021-04-10 09:00:38 +02001935 if (!(match ^ inverse)) {
1936 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001937 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001938 }
1939
1940 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001941
1942 out:
1943 free_trash_chunk(tmp);
1944 free_trash_chunk(nbuf);
1945 free_trash_chunk(vbuf);
1946 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001947 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001948 return ret;
1949
1950 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001951 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001952 ret = TCPCHK_EVAL_STOP;
1953 msg = alloc_trash_chunk();
1954 if (msg)
1955 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1956 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1957 goto out;
1958
1959 wait_more_data:
1960 ret = TCPCHK_EVAL_WAIT;
1961 goto out;
1962}
1963
1964/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1965 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1966 * if an error occurred.
1967 */
1968enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1969{
1970 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1971 struct tcpcheck_expect *expect = &rule->expect;
1972 struct buffer *msg = NULL, *tmp = NULL;
1973 struct ist desc = IST_NULL;
1974 enum healthcheck_status status;
1975 int match, inverse;
1976
Christopher Faulet147b8c92021-04-10 09:00:38 +02001977 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1978
Willy Tarreau51cd5952020-06-05 12:25:38 +02001979 last_read |= b_full(&check->bi);
1980
1981 /* The current expect might need more data than the previous one, check again
1982 * that the minimum amount data required to match is respected.
1983 */
1984 if (!last_read) {
1985 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1986 (b_data(&check->bi) < istlen(expect->data))) {
1987 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001988 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001989 goto out;
1990 }
1991 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1992 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001993 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001994 goto out;
1995 }
1996 }
1997
1998 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1999 /* Make GCC happy ; initialize match to a failure state. */
2000 match = inverse;
2001 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
2002
2003 switch (expect->type) {
2004 case TCPCHK_EXPECT_STRING:
2005 case TCPCHK_EXPECT_BINARY:
2006 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2007 break;
2008 case TCPCHK_EXPECT_STRING_REGEX:
2009 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2010 break;
2011
2012 case TCPCHK_EXPECT_BINARY_REGEX:
2013 chunk_reset(&trash);
2014 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2015 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2016 break;
2017
2018 case TCPCHK_EXPECT_STRING_LF:
2019 case TCPCHK_EXPECT_BINARY_LF:
2020 match = 0;
2021 tmp = alloc_trash_chunk();
2022 if (!tmp) {
2023 status = HCHK_STATUS_L7RSP;
2024 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002025 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002026 goto error;
2027 }
2028 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2029 if (!b_data(tmp)) {
2030 status = HCHK_STATUS_L7RSP;
2031 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002032 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002033 goto error;
2034 }
2035 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2036 int len = tmp->data;
2037 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2038 status = HCHK_STATUS_L7RSP;
2039 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002040 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002041 goto error;
2042 }
2043 tmp->data = len;
2044 }
2045 if (b_data(&check->bi) < tmp->data) {
2046 if (!last_read) {
2047 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002048 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002049 goto out;
2050 }
2051 break;
2052 }
2053 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2054 break;
2055
2056 case TCPCHK_EXPECT_CUSTOM:
2057 if (expect->custom)
2058 ret = expect->custom(check, rule, last_read);
2059 goto out;
2060 default:
2061 /* Should never happen. */
2062 ret = TCPCHK_EVAL_STOP;
2063 goto out;
2064 }
2065
2066
2067 /* Wait for more data on mismatch only if no minimum is defined (-1),
2068 * otherwise the absence of match is already conclusive.
2069 */
2070 if (!match && !last_read && (expect->min_recv == -1)) {
2071 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002072 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002073 goto out;
2074 }
2075
2076 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002077 if (match ^ inverse) {
2078 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002079 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002080 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002081
2082 error:
2083 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002084 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002085 ret = TCPCHK_EVAL_STOP;
2086 msg = alloc_trash_chunk();
2087 if (msg)
2088 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2089 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2090 free_trash_chunk(msg);
2091
2092 out:
2093 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002094 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002095 return ret;
2096}
2097
2098/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2099 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2100 * waits.
2101 */
2102enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2103{
2104 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2105 struct act_rule *act_rule;
2106 enum act_return act_ret;
2107
2108 act_rule =rule->action_kw.rule;
2109 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2110 if (act_ret != ACT_RET_CONT) {
2111 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2112 tcpcheck_get_step_id(check, rule));
2113 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2114 ret = TCPCHK_EVAL_STOP;
2115 }
2116
2117 return ret;
2118}
2119
2120/* Executes a tcp-check ruleset. Note that this is called both from the
2121 * connection's wake() callback and from the check scheduling task. It returns
2122 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2123 * presenting the risk of an fd replacement.
2124 *
2125 * Please do NOT place any return statement in this function and only leave
2126 * via the out_end_tcpcheck label after setting retcode.
2127 */
2128int tcpcheck_main(struct check *check)
2129{
2130 struct tcpcheck_rule *rule;
2131 struct conn_stream *cs = check->cs;
2132 struct connection *conn = cs_conn(cs);
2133 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002134 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002135 enum tcpcheck_eval_ret eval_ret;
2136
2137 /* here, we know that the check is complete or that it failed */
2138 if (check->result != CHK_RES_UNKNOWN)
2139 goto out;
2140
Christopher Faulet147b8c92021-04-10 09:00:38 +02002141 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2142
Willy Tarreau51cd5952020-06-05 12:25:38 +02002143 /* Note: the conn-stream and the connection may only be undefined before
2144 * the first rule evaluation (it is always a connect rule) or when the
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002145 * conn-stream allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002146 */
2147
2148 /* 1- check for connection error, if any */
Christopher Fauletb041b232022-03-24 10:27:02 +01002149 if ((conn && conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002150 goto out_end_tcpcheck;
2151
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002152 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002153 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002154 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002155 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002156 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2157 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002158
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002159 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002160 * tcp-check variables */
2161 else {
2162 struct tcpcheck_var *var;
2163
2164 /* First evaluation, create a session */
2165 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2166 if (!check->sess) {
2167 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002168 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002169 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2170 goto out_end_tcpcheck;
2171 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002172 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002173 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2174
2175 /* Preset tcp-check variables */
2176 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2177 struct sample smp;
2178
2179 memset(&smp, 0, sizeof(smp));
2180 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2181 smp.data = var->data;
2182 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2183 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002184 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002185 }
2186
2187 /* Now evaluate the tcp-check rules */
2188
2189 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2190 check->code = 0;
2191 switch (rule->action) {
2192 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002193 /* Not the first connection, release it first */
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002194 if (cs_conn(cs) && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002195 check->state |= CHK_ST_CLOSE_CONN;
2196 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002197 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002198
2199 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002200
2201 /* We are still waiting the connection gets closed */
2202 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002203 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002204 eval_ret = TCPCHK_EVAL_WAIT;
2205 break;
2206 }
2207
Christopher Faulet147b8c92021-04-10 09:00:38 +02002208 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002209 eval_ret = tcpcheck_eval_connect(check, rule);
2210
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002211 /* Refresh connection */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002212 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002213 last_read = 0;
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002214 must_read = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002215 break;
2216 case TCPCHK_ACT_SEND:
2217 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002218 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002219 eval_ret = tcpcheck_eval_send(check, rule);
2220 must_read = 1;
2221 break;
2222 case TCPCHK_ACT_EXPECT:
2223 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002224 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002225 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002226 eval_ret = tcpcheck_eval_recv(check, rule);
2227 if (eval_ret == TCPCHK_EVAL_STOP)
2228 goto out_end_tcpcheck;
2229 else if (eval_ret == TCPCHK_EVAL_WAIT)
2230 goto out;
Christopher Fauletb041b232022-03-24 10:27:02 +01002231 last_read = ((conn->flags & CO_FL_ERROR) || (cs->endp->flags & (CS_EP_ERROR|CS_EP_EOS)));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002232 must_read = 0;
2233 }
2234
2235 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2236 ? tcpcheck_eval_expect_http(check, rule, last_read)
2237 : tcpcheck_eval_expect(check, rule, last_read));
2238
2239 if (eval_ret == TCPCHK_EVAL_WAIT) {
2240 check->current_step = rule->expect.head;
2241 if (!(check->wait_list.events & SUB_RETRY_RECV))
2242 conn->mux->subscribe(cs, SUB_RETRY_RECV, &check->wait_list);
2243 }
2244 break;
2245 case TCPCHK_ACT_ACTION_KW:
2246 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002247 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002248 eval_ret = tcpcheck_eval_action_kw(check, rule);
2249 break;
2250 default:
2251 /* Otherwise, just go to the next one and don't update
2252 * the current step
2253 */
2254 eval_ret = TCPCHK_EVAL_CONTINUE;
2255 break;
2256 }
2257
2258 switch (eval_ret) {
2259 case TCPCHK_EVAL_CONTINUE:
2260 break;
2261 case TCPCHK_EVAL_WAIT:
2262 goto out;
2263 case TCPCHK_EVAL_STOP:
2264 goto out_end_tcpcheck;
2265 }
2266 }
2267
2268 /* All rules was evaluated */
2269 if (check->current_step) {
2270 rule = check->current_step;
2271
Christopher Faulet147b8c92021-04-10 09:00:38 +02002272 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2273
Willy Tarreau51cd5952020-06-05 12:25:38 +02002274 if (rule->action == TCPCHK_ACT_EXPECT) {
2275 struct buffer *msg;
2276 enum healthcheck_status status;
2277
2278 if (check->server &&
2279 (check->server->proxy->options & PR_O_DISABLE404) &&
2280 (check->server->next_state != SRV_ST_STOPPED) &&
2281 (check->code == 404)) {
2282 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002283 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002284 goto out_end_tcpcheck;
2285 }
2286
2287 msg = alloc_trash_chunk();
2288 if (msg)
2289 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2290 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2291 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2292 free_trash_chunk(msg);
2293 }
2294 else if (rule->action == TCPCHK_ACT_CONNECT) {
2295 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2296 enum healthcheck_status status = HCHK_STATUS_L4OK;
2297#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002298 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002299 status = HCHK_STATUS_L6OK;
2300#endif
2301 set_server_check_status(check, status, msg);
2302 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002303 else
2304 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002305 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002306 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002307 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002308 }
2309 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002310
2311 out_end_tcpcheck:
Christopher Fauletb041b232022-03-24 10:27:02 +01002312 if ((conn && conn->flags & CO_FL_ERROR) || (cs->endp->flags & CS_EP_ERROR)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002313 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002314 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002315 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002316
Christopher Fauletb381a502020-11-25 13:47:00 +01002317 /* the tcpcheck is finished, release in/out buffer now */
2318 check_release_buf(check, &check->bi);
2319 check_release_buf(check, &check->bo);
2320
Willy Tarreau51cd5952020-06-05 12:25:38 +02002321 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002322 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002323 return retcode;
2324}
2325
Willy Tarreaua631b862022-03-02 14:54:44 +01002326void tcp_check_keywords_register(struct action_kw_list *kw_list)
2327{
2328 LIST_APPEND(&tcp_check_keywords.list, &kw_list->list);
2329}
Willy Tarreau51cd5952020-06-05 12:25:38 +02002330
2331/**************************************************************************/
2332/******************* Internals to parse tcp-check rules *******************/
2333/**************************************************************************/
2334struct action_kw_list tcp_check_keywords = {
2335 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2336};
2337
2338/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2339 * returned on error.
2340 */
2341struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2342 struct list *rules, struct action_kw *kw,
2343 const char *file, int line, char **errmsg)
2344{
2345 struct tcpcheck_rule *chk = NULL;
2346 struct act_rule *actrule = NULL;
2347
Willy Tarreaud535f802021-10-11 08:49:26 +02002348 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002349 if (!actrule) {
2350 memprintf(errmsg, "out of memory");
2351 goto error;
2352 }
2353 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002354
2355 cur_arg++;
2356 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2357 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2358 goto error;
2359 }
2360
2361 chk = calloc(1, sizeof(*chk));
2362 if (!chk) {
2363 memprintf(errmsg, "out of memory");
2364 goto error;
2365 }
2366 chk->action = TCPCHK_ACT_ACTION_KW;
2367 chk->action_kw.rule = actrule;
2368 return chk;
2369
2370 error:
2371 free(actrule);
2372 return NULL;
2373}
2374
2375/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2376 * returned on error.
2377 */
2378struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2379 const char *file, int line, char **errmsg)
2380{
2381 struct tcpcheck_rule *chk = NULL;
2382 struct sockaddr_storage *sk = NULL;
2383 char *comment = NULL, *sni = NULL, *alpn = NULL;
2384 struct sample_expr *port_expr = NULL;
2385 const struct mux_proto_list *mux_proto = NULL;
2386 unsigned short conn_opts = 0;
2387 long port = 0;
2388 int alpn_len = 0;
2389
2390 list_for_each_entry(chk, rules, list) {
2391 if (chk->action == TCPCHK_ACT_CONNECT)
2392 break;
2393 if (chk->action == TCPCHK_ACT_COMMENT ||
2394 chk->action == TCPCHK_ACT_ACTION_KW ||
2395 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2396 continue;
2397
2398 memprintf(errmsg, "first step MUST also be a 'connect', "
2399 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2400 "when there is a 'connect' step in the tcp-check ruleset");
2401 goto error;
2402 }
2403
2404 cur_arg++;
2405 while (*(args[cur_arg])) {
2406 if (strcmp(args[cur_arg], "default") == 0)
2407 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2408 else if (strcmp(args[cur_arg], "addr") == 0) {
2409 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002410
2411 if (!*(args[cur_arg+1])) {
2412 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2413 goto error;
2414 }
2415
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002416 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2417 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002418 if (!sk) {
2419 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2420 goto error;
2421 }
2422
Willy Tarreau51cd5952020-06-05 12:25:38 +02002423 cur_arg++;
2424 }
2425 else if (strcmp(args[cur_arg], "port") == 0) {
2426 const char *p, *end;
2427
2428 if (!*(args[cur_arg+1])) {
2429 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2430 goto error;
2431 }
2432 cur_arg++;
2433
2434 port = 0;
2435 release_sample_expr(port_expr);
2436 p = args[cur_arg]; end = p + strlen(p);
2437 port = read_uint(&p, end);
2438 if (p != end) {
2439 int idx = 0;
2440
2441 px->conf.args.ctx = ARGC_SRV;
2442 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002443 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002444
2445 if (!port_expr) {
2446 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2447 goto error;
2448 }
2449 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2450 memprintf(errmsg, "error detected while parsing port expression : "
2451 " fetch method '%s' extracts information from '%s', "
2452 "none of which is available here.\n",
2453 args[cur_arg], sample_src_names(port_expr->fetch->use));
2454 goto error;
2455 }
2456 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2457 }
2458 else if (port > 65535 || port < 1) {
2459 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2460 args[cur_arg]);
2461 goto error;
2462 }
2463 }
2464 else if (strcmp(args[cur_arg], "proto") == 0) {
2465 if (!*(args[cur_arg+1])) {
2466 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2467 goto error;
2468 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002469 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002470 if (!mux_proto) {
2471 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2472 goto error;
2473 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002474
2475 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2476 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2477 goto error;
2478 }
2479 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2480 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2481 goto error;
2482 }
2483
Willy Tarreau51cd5952020-06-05 12:25:38 +02002484 cur_arg++;
2485 }
2486 else if (strcmp(args[cur_arg], "comment") == 0) {
2487 if (!*(args[cur_arg+1])) {
2488 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2489 goto error;
2490 }
2491 cur_arg++;
2492 free(comment);
2493 comment = strdup(args[cur_arg]);
2494 if (!comment) {
2495 memprintf(errmsg, "out of memory");
2496 goto error;
2497 }
2498 }
2499 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2500 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2501 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2502 conn_opts |= TCPCHK_OPT_SOCKS4;
2503 else if (strcmp(args[cur_arg], "linger") == 0)
2504 conn_opts |= TCPCHK_OPT_LINGER;
2505#ifdef USE_OPENSSL
2506 else if (strcmp(args[cur_arg], "ssl") == 0) {
2507 px->options |= PR_O_TCPCHK_SSL;
2508 conn_opts |= TCPCHK_OPT_SSL;
2509 }
2510 else if (strcmp(args[cur_arg], "sni") == 0) {
2511 if (!*(args[cur_arg+1])) {
2512 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2513 goto error;
2514 }
2515 cur_arg++;
2516 free(sni);
2517 sni = strdup(args[cur_arg]);
2518 if (!sni) {
2519 memprintf(errmsg, "out of memory");
2520 goto error;
2521 }
2522 }
2523 else if (strcmp(args[cur_arg], "alpn") == 0) {
2524#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2525 free(alpn);
2526 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2527 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2528 goto error;
2529 }
2530 cur_arg++;
2531#else
2532 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2533 goto error;
2534#endif
2535 }
2536#endif /* USE_OPENSSL */
2537
2538 else {
2539 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2540#ifdef USE_OPENSSL
2541 ", 'ssl', 'sni', 'alpn'"
2542#endif /* USE_OPENSSL */
2543 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2544 args[cur_arg]);
2545 goto error;
2546 }
2547 cur_arg++;
2548 }
2549
2550 chk = calloc(1, sizeof(*chk));
2551 if (!chk) {
2552 memprintf(errmsg, "out of memory");
2553 goto error;
2554 }
2555 chk->action = TCPCHK_ACT_CONNECT;
2556 chk->comment = comment;
2557 chk->connect.port = port;
2558 chk->connect.options = conn_opts;
2559 chk->connect.sni = sni;
2560 chk->connect.alpn = alpn;
2561 chk->connect.alpn_len= alpn_len;
2562 chk->connect.port_expr= port_expr;
2563 chk->connect.mux_proto= mux_proto;
2564 if (sk)
2565 chk->connect.addr = *sk;
2566 return chk;
2567
2568 error:
2569 free(alpn);
2570 free(sni);
2571 free(comment);
2572 release_sample_expr(port_expr);
2573 return NULL;
2574}
2575
2576/* Parses and creates a tcp-check send rule. NULL is returned on error */
2577struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2578 const char *file, int line, char **errmsg)
2579{
2580 struct tcpcheck_rule *chk = NULL;
2581 char *comment = NULL, *data = NULL;
2582 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2583
2584 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2585 type = TCPCHK_SEND_BINARY_LF;
2586 else if (strcmp(args[cur_arg], "send-binary") == 0)
2587 type = TCPCHK_SEND_BINARY;
2588 else if (strcmp(args[cur_arg], "send-lf") == 0)
2589 type = TCPCHK_SEND_STRING_LF;
2590 else if (strcmp(args[cur_arg], "send") == 0)
2591 type = TCPCHK_SEND_STRING;
2592
2593 if (!*(args[cur_arg+1])) {
2594 memprintf(errmsg, "'%s' expects a %s as argument",
2595 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2596 goto error;
2597 }
2598
2599 data = args[cur_arg+1];
2600
2601 cur_arg += 2;
2602 while (*(args[cur_arg])) {
2603 if (strcmp(args[cur_arg], "comment") == 0) {
2604 if (!*(args[cur_arg+1])) {
2605 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2606 goto error;
2607 }
2608 cur_arg++;
2609 free(comment);
2610 comment = strdup(args[cur_arg]);
2611 if (!comment) {
2612 memprintf(errmsg, "out of memory");
2613 goto error;
2614 }
2615 }
2616 else {
2617 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2618 args[cur_arg]);
2619 goto error;
2620 }
2621 cur_arg++;
2622 }
2623
2624 chk = calloc(1, sizeof(*chk));
2625 if (!chk) {
2626 memprintf(errmsg, "out of memory");
2627 goto error;
2628 }
2629 chk->action = TCPCHK_ACT_SEND;
2630 chk->comment = comment;
2631 chk->send.type = type;
2632
2633 switch (chk->send.type) {
2634 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002635 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002636 if (!isttest(chk->send.data)) {
2637 memprintf(errmsg, "out of memory");
2638 goto error;
2639 }
2640 break;
2641 case TCPCHK_SEND_BINARY: {
2642 int len = chk->send.data.len;
2643 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2644 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2645 goto error;
2646 }
2647 chk->send.data.len = len;
2648 break;
2649 }
2650 case TCPCHK_SEND_STRING_LF:
2651 case TCPCHK_SEND_BINARY_LF:
2652 LIST_INIT(&chk->send.fmt);
2653 px->conf.args.ctx = ARGC_SRV;
2654 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2655 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2656 goto error;
2657 }
2658 break;
2659 case TCPCHK_SEND_HTTP:
2660 case TCPCHK_SEND_UNDEF:
2661 goto error;
2662 }
2663
2664 return chk;
2665
2666 error:
2667 free(chk);
2668 free(comment);
2669 return NULL;
2670}
2671
2672/* Parses and creates a http-check send rule. NULL is returned on error */
2673struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2674 const char *file, int line, char **errmsg)
2675{
2676 struct tcpcheck_rule *chk = NULL;
2677 struct tcpcheck_http_hdr *hdr = NULL;
2678 struct http_hdr hdrs[global.tune.max_http_hdr];
2679 char *meth = NULL, *uri = NULL, *vsn = NULL;
2680 char *body = NULL, *comment = NULL;
2681 unsigned int flags = 0;
2682 int i = 0, host_hdr = -1;
2683
2684 cur_arg++;
2685 while (*(args[cur_arg])) {
2686 if (strcmp(args[cur_arg], "meth") == 0) {
2687 if (!*(args[cur_arg+1])) {
2688 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2689 goto error;
2690 }
2691 cur_arg++;
2692 meth = args[cur_arg];
2693 }
2694 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2695 if (!*(args[cur_arg+1])) {
2696 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2697 goto error;
2698 }
2699 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2700 if (strcmp(args[cur_arg], "uri-lf") == 0)
2701 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2702 cur_arg++;
2703 uri = args[cur_arg];
2704 }
2705 else if (strcmp(args[cur_arg], "ver") == 0) {
2706 if (!*(args[cur_arg+1])) {
2707 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2708 goto error;
2709 }
2710 cur_arg++;
2711 vsn = args[cur_arg];
2712 }
2713 else if (strcmp(args[cur_arg], "hdr") == 0) {
2714 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2715 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2716 goto error;
2717 }
2718
2719 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2720 if (host_hdr >= 0) {
2721 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2722 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2723 goto error;
2724 }
2725 host_hdr = i;
2726 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002727 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002728 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2729 goto skip_hdr;
2730
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002731 hdrs[i].n = ist(args[cur_arg + 1]);
2732 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002733 i++;
2734 skip_hdr:
2735 cur_arg += 2;
2736 }
2737 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2738 if (!*(args[cur_arg+1])) {
2739 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2740 goto error;
2741 }
2742 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2743 if (strcmp(args[cur_arg], "body-lf") == 0)
2744 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2745 cur_arg++;
2746 body = args[cur_arg];
2747 }
2748 else if (strcmp(args[cur_arg], "comment") == 0) {
2749 if (!*(args[cur_arg+1])) {
2750 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2751 goto error;
2752 }
2753 cur_arg++;
2754 free(comment);
2755 comment = strdup(args[cur_arg]);
2756 if (!comment) {
2757 memprintf(errmsg, "out of memory");
2758 goto error;
2759 }
2760 }
2761 else {
2762 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2763 " but got '%s' as argument.", args[cur_arg]);
2764 goto error;
2765 }
2766 cur_arg++;
2767 }
2768
2769 hdrs[i].n = hdrs[i].v = IST_NULL;
2770
2771 chk = calloc(1, sizeof(*chk));
2772 if (!chk) {
2773 memprintf(errmsg, "out of memory");
2774 goto error;
2775 }
2776 chk->action = TCPCHK_ACT_SEND;
2777 chk->comment = comment; comment = NULL;
2778 chk->send.type = TCPCHK_SEND_HTTP;
2779 chk->send.http.flags = flags;
2780 LIST_INIT(&chk->send.http.hdrs);
2781
2782 if (meth) {
2783 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2784 chk->send.http.meth.str.area = strdup(meth);
2785 chk->send.http.meth.str.data = strlen(meth);
2786 if (!chk->send.http.meth.str.area) {
2787 memprintf(errmsg, "out of memory");
2788 goto error;
2789 }
2790 }
2791 if (uri) {
2792 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2793 LIST_INIT(&chk->send.http.uri_fmt);
2794 px->conf.args.ctx = ARGC_SRV;
2795 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2796 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2797 goto error;
2798 }
2799 }
2800 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002801 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002802 if (!isttest(chk->send.http.uri)) {
2803 memprintf(errmsg, "out of memory");
2804 goto error;
2805 }
2806 }
2807 }
2808 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002809 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002810 if (!isttest(chk->send.http.vsn)) {
2811 memprintf(errmsg, "out of memory");
2812 goto error;
2813 }
2814 }
2815 for (i = 0; istlen(hdrs[i].n); i++) {
2816 hdr = calloc(1, sizeof(*hdr));
2817 if (!hdr) {
2818 memprintf(errmsg, "out of memory");
2819 goto error;
2820 }
2821 LIST_INIT(&hdr->value);
2822 hdr->name = istdup(hdrs[i].n);
2823 if (!isttest(hdr->name)) {
2824 memprintf(errmsg, "out of memory");
2825 goto error;
2826 }
2827
2828 ist0(hdrs[i].v);
2829 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2830 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002831 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002832 hdr = NULL;
2833 }
2834
2835 if (body) {
2836 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2837 LIST_INIT(&chk->send.http.body_fmt);
2838 px->conf.args.ctx = ARGC_SRV;
2839 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2840 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2841 goto error;
2842 }
2843 }
2844 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002845 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002846 if (!isttest(chk->send.http.body)) {
2847 memprintf(errmsg, "out of memory");
2848 goto error;
2849 }
2850 }
2851 }
2852
2853 return chk;
2854
2855 error:
2856 free_tcpcheck_http_hdr(hdr);
2857 free_tcpcheck(chk, 0);
2858 free(comment);
2859 return NULL;
2860}
2861
2862/* Parses and creates a http-check comment rule. NULL is returned on error */
2863struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2864 const char *file, int line, char **errmsg)
2865{
2866 struct tcpcheck_rule *chk = NULL;
2867 char *comment = NULL;
2868
2869 if (!*(args[cur_arg+1])) {
2870 memprintf(errmsg, "expects a string as argument");
2871 goto error;
2872 }
2873 cur_arg++;
2874 comment = strdup(args[cur_arg]);
2875 if (!comment) {
2876 memprintf(errmsg, "out of memory");
2877 goto error;
2878 }
2879
2880 chk = calloc(1, sizeof(*chk));
2881 if (!chk) {
2882 memprintf(errmsg, "out of memory");
2883 goto error;
2884 }
2885 chk->action = TCPCHK_ACT_COMMENT;
2886 chk->comment = comment;
2887 return chk;
2888
2889 error:
2890 free(comment);
2891 return NULL;
2892}
2893
2894/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2895 * on error. <proto> is set to the right protocol flags (covered by the
2896 * TCPCHK_RULES_PROTO_CHK mask).
2897 */
2898struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2899 struct list *rules, unsigned int proto,
2900 const char *file, int line, char **errmsg)
2901{
2902 struct tcpcheck_rule *prev_check, *chk = NULL;
2903 struct sample_expr *status_expr = NULL;
2904 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2905 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2906 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2907 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2908 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2909 unsigned int flags = 0;
2910 long min_recv = -1;
2911 int inverse = 0;
2912
2913 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2914 if (!*(args[cur_arg+1])) {
2915 memprintf(errmsg, "expects at least a matching pattern as arguments");
2916 goto error;
2917 }
2918
2919 cur_arg++;
2920 while (*(args[cur_arg])) {
2921 int in_pattern = 0;
2922
2923 rescan:
2924 if (strcmp(args[cur_arg], "min-recv") == 0) {
2925 if (in_pattern) {
2926 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2927 goto error;
2928 }
2929 if (!*(args[cur_arg+1])) {
2930 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2931 goto error;
2932 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002933 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002934 cur_arg++;
2935 min_recv = atol(args[cur_arg]);
2936 if (min_recv < -1 || min_recv > INT_MAX) {
2937 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2938 goto error;
2939 }
2940 }
2941 else if (*(args[cur_arg]) == '!') {
2942 in_pattern = 1;
2943 while (*(args[cur_arg]) == '!') {
2944 inverse = !inverse;
2945 args[cur_arg]++;
2946 }
2947 if (!*(args[cur_arg]))
2948 cur_arg++;
2949 goto rescan;
2950 }
2951 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2952 if (type != TCPCHK_EXPECT_UNDEF) {
2953 memprintf(errmsg, "only on pattern expected");
2954 goto error;
2955 }
2956 if (proto != TCPCHK_RULES_HTTP_CHK)
2957 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2958 else
2959 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2960
2961 if (!*(args[cur_arg+1])) {
2962 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2963 goto error;
2964 }
2965 cur_arg++;
2966 pattern = args[cur_arg];
2967 }
2968 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2969 if (proto == TCPCHK_RULES_HTTP_CHK)
2970 goto bad_http_kw;
2971 if (type != TCPCHK_EXPECT_UNDEF) {
2972 memprintf(errmsg, "only on pattern expected");
2973 goto error;
2974 }
2975 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2976
2977 if (!*(args[cur_arg+1])) {
2978 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2979 goto error;
2980 }
2981 cur_arg++;
2982 pattern = args[cur_arg];
2983 }
2984 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2985 if (type != TCPCHK_EXPECT_UNDEF) {
2986 memprintf(errmsg, "only on pattern expected");
2987 goto error;
2988 }
2989 if (proto != TCPCHK_RULES_HTTP_CHK)
2990 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2991 else {
2992 if (*(args[cur_arg]) != 's')
2993 goto bad_http_kw;
2994 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2995 }
2996
2997 if (!*(args[cur_arg+1])) {
2998 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2999 goto error;
3000 }
3001 cur_arg++;
3002 pattern = args[cur_arg];
3003 }
3004 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3005 if (proto != TCPCHK_RULES_HTTP_CHK)
3006 goto bad_tcp_kw;
3007 if (type != TCPCHK_EXPECT_UNDEF) {
3008 memprintf(errmsg, "only on pattern expected");
3009 goto error;
3010 }
3011 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3012
3013 if (!*(args[cur_arg+1])) {
3014 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3015 goto error;
3016 }
3017 cur_arg++;
3018 pattern = args[cur_arg];
3019 }
3020 else if (strcmp(args[cur_arg], "custom") == 0) {
3021 if (in_pattern) {
3022 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3023 goto error;
3024 }
3025 if (type != TCPCHK_EXPECT_UNDEF) {
3026 memprintf(errmsg, "only on pattern expected");
3027 goto error;
3028 }
3029 type = TCPCHK_EXPECT_CUSTOM;
3030 }
3031 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3032 int orig_arg = cur_arg;
3033
3034 if (proto != TCPCHK_RULES_HTTP_CHK)
3035 goto bad_tcp_kw;
3036 if (type != TCPCHK_EXPECT_UNDEF) {
3037 memprintf(errmsg, "only on pattern expected");
3038 goto error;
3039 }
3040 type = TCPCHK_EXPECT_HTTP_HEADER;
3041
3042 if (strcmp(args[cur_arg], "fhdr") == 0)
3043 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3044
3045 /* Parse the name pattern, mandatory */
3046 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3047 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3048 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3049 args[orig_arg]);
3050 goto error;
3051 }
3052
3053 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3054 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3055
3056 cur_arg += 2;
3057 if (strcmp(args[cur_arg], "-m") == 0) {
3058 if (!*(args[cur_arg+1])) {
3059 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3060 args[orig_arg], args[cur_arg]);
3061 goto error;
3062 }
3063 if (strcmp(args[cur_arg+1], "str") == 0)
3064 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3065 else if (strcmp(args[cur_arg+1], "beg") == 0)
3066 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3067 else if (strcmp(args[cur_arg+1], "end") == 0)
3068 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3069 else if (strcmp(args[cur_arg+1], "sub") == 0)
3070 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3071 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3072 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3073 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3074 args[orig_arg]);
3075 goto error;
3076 }
3077 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3078 }
3079 else {
3080 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3081 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3082 goto error;
3083 }
3084 cur_arg += 2;
3085 }
3086 else
3087 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3088 npat = args[cur_arg];
3089
3090 if (!*(args[cur_arg+1]) ||
3091 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3092 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3093 goto next;
3094 }
3095 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3096 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3097
3098 /* Parse the value pattern, optional */
3099 if (strcmp(args[cur_arg+2], "-m") == 0) {
3100 cur_arg += 2;
3101 if (!*(args[cur_arg+1])) {
3102 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3103 args[orig_arg], args[cur_arg]);
3104 goto error;
3105 }
3106 if (strcmp(args[cur_arg+1], "str") == 0)
3107 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3108 else if (strcmp(args[cur_arg+1], "beg") == 0)
3109 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3110 else if (strcmp(args[cur_arg+1], "end") == 0)
3111 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3112 else if (strcmp(args[cur_arg+1], "sub") == 0)
3113 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3114 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3115 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3116 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3117 args[orig_arg]);
3118 goto error;
3119 }
3120 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3121 }
3122 else {
3123 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3124 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3125 goto error;
3126 }
3127 }
3128 else
3129 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3130
3131 if (!*(args[cur_arg+2])) {
3132 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3133 goto error;
3134 }
3135 vpat = args[cur_arg+2];
3136 cur_arg += 2;
3137 }
3138 else if (strcmp(args[cur_arg], "comment") == 0) {
3139 if (in_pattern) {
3140 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3141 goto error;
3142 }
3143 if (!*(args[cur_arg+1])) {
3144 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3145 goto error;
3146 }
3147 cur_arg++;
3148 free(comment);
3149 comment = strdup(args[cur_arg]);
3150 if (!comment) {
3151 memprintf(errmsg, "out of memory");
3152 goto error;
3153 }
3154 }
3155 else if (strcmp(args[cur_arg], "on-success") == 0) {
3156 if (in_pattern) {
3157 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3158 goto error;
3159 }
3160 if (!*(args[cur_arg+1])) {
3161 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3162 goto error;
3163 }
3164 cur_arg++;
3165 on_success_msg = args[cur_arg];
3166 }
3167 else if (strcmp(args[cur_arg], "on-error") == 0) {
3168 if (in_pattern) {
3169 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3170 goto error;
3171 }
3172 if (!*(args[cur_arg+1])) {
3173 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3174 goto error;
3175 }
3176 cur_arg++;
3177 on_error_msg = args[cur_arg];
3178 }
3179 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3180 if (in_pattern) {
3181 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3182 goto error;
3183 }
3184 if (!*(args[cur_arg+1])) {
3185 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3186 goto error;
3187 }
3188 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3189 ok_st = HCHK_STATUS_L7OKD;
3190 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3191 ok_st = HCHK_STATUS_L7OKCD;
3192 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3193 ok_st = HCHK_STATUS_L6OK;
3194 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3195 ok_st = HCHK_STATUS_L4OK;
3196 else {
3197 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3198 args[cur_arg], args[cur_arg+1]);
3199 goto error;
3200 }
3201 cur_arg++;
3202 }
3203 else if (strcmp(args[cur_arg], "error-status") == 0) {
3204 if (in_pattern) {
3205 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3206 goto error;
3207 }
3208 if (!*(args[cur_arg+1])) {
3209 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3210 goto error;
3211 }
3212 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3213 err_st = HCHK_STATUS_L7RSP;
3214 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3215 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003216 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3217 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003218 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3219 err_st = HCHK_STATUS_L6RSP;
3220 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3221 err_st = HCHK_STATUS_L4CON;
3222 else {
3223 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3224 args[cur_arg], args[cur_arg+1]);
3225 goto error;
3226 }
3227 cur_arg++;
3228 }
3229 else if (strcmp(args[cur_arg], "status-code") == 0) {
3230 int idx = 0;
3231
3232 if (in_pattern) {
3233 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3234 goto error;
3235 }
3236 if (!*(args[cur_arg+1])) {
3237 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3238 goto error;
3239 }
3240
3241 cur_arg++;
3242 release_sample_expr(status_expr);
3243 px->conf.args.ctx = ARGC_SRV;
3244 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003245 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003246 if (!status_expr) {
3247 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3248 goto error;
3249 }
3250 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3251 memprintf(errmsg, "error detected while parsing status-code expression : "
3252 " fetch method '%s' extracts information from '%s', "
3253 "none of which is available here.\n",
3254 args[cur_arg], sample_src_names(status_expr->fetch->use));
3255 goto error;
3256 }
3257 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3258 }
3259 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3260 if (in_pattern) {
3261 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3262 goto error;
3263 }
3264 if (!*(args[cur_arg+1])) {
3265 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3266 goto error;
3267 }
3268 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3269 tout_st = HCHK_STATUS_L7TOUT;
3270 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3271 tout_st = HCHK_STATUS_L6TOUT;
3272 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3273 tout_st = HCHK_STATUS_L4TOUT;
3274 else {
3275 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3276 args[cur_arg], args[cur_arg+1]);
3277 goto error;
3278 }
3279 cur_arg++;
3280 }
3281 else {
3282 if (proto == TCPCHK_RULES_HTTP_CHK) {
3283 bad_http_kw:
3284 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3285 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3286 }
3287 else {
3288 bad_tcp_kw:
3289 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3290 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3291 }
3292 goto error;
3293 }
3294 next:
3295 cur_arg++;
3296 }
3297
3298 chk = calloc(1, sizeof(*chk));
3299 if (!chk) {
3300 memprintf(errmsg, "out of memory");
3301 goto error;
3302 }
3303 chk->action = TCPCHK_ACT_EXPECT;
3304 LIST_INIT(&chk->expect.onerror_fmt);
3305 LIST_INIT(&chk->expect.onsuccess_fmt);
3306 chk->comment = comment; comment = NULL;
3307 chk->expect.type = type;
3308 chk->expect.min_recv = min_recv;
3309 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3310 chk->expect.ok_status = ok_st;
3311 chk->expect.err_status = err_st;
3312 chk->expect.tout_status = tout_st;
3313 chk->expect.status_expr = status_expr; status_expr = NULL;
3314
3315 if (on_success_msg) {
3316 px->conf.args.ctx = ARGC_SRV;
3317 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3318 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3319 goto error;
3320 }
3321 }
3322 if (on_error_msg) {
3323 px->conf.args.ctx = ARGC_SRV;
3324 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3325 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3326 goto error;
3327 }
3328 }
3329
3330 switch (chk->expect.type) {
3331 case TCPCHK_EXPECT_HTTP_STATUS: {
3332 const char *p = pattern;
3333 unsigned int c1,c2;
3334
3335 chk->expect.codes.codes = NULL;
3336 chk->expect.codes.num = 0;
3337 while (1) {
3338 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3339 if (*p == '-') {
3340 p++;
3341 c2 = read_uint(&p, pattern + strlen(pattern));
3342 }
3343 if (c1 > c2) {
3344 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3345 goto error;
3346 }
3347
3348 chk->expect.codes.num++;
3349 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3350 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3351 if (!chk->expect.codes.codes) {
3352 memprintf(errmsg, "out of memory");
3353 goto error;
3354 }
3355 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3356 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3357
3358 if (*p == '\0')
3359 break;
3360 if (*p != ',') {
3361 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3362 goto error;
3363 }
3364 p++;
3365 }
3366 break;
3367 }
3368 case TCPCHK_EXPECT_STRING:
3369 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003370 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003371 if (!isttest(chk->expect.data)) {
3372 memprintf(errmsg, "out of memory");
3373 goto error;
3374 }
3375 break;
3376 case TCPCHK_EXPECT_BINARY: {
3377 int len = chk->expect.data.len;
3378
3379 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3380 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3381 goto error;
3382 }
3383 chk->expect.data.len = len;
3384 break;
3385 }
3386 case TCPCHK_EXPECT_STRING_REGEX:
3387 case TCPCHK_EXPECT_BINARY_REGEX:
3388 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3389 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3390 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3391 if (!chk->expect.regex)
3392 goto error;
3393 break;
3394
3395 case TCPCHK_EXPECT_STRING_LF:
3396 case TCPCHK_EXPECT_BINARY_LF:
3397 case TCPCHK_EXPECT_HTTP_BODY_LF:
3398 LIST_INIT(&chk->expect.fmt);
3399 px->conf.args.ctx = ARGC_SRV;
3400 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3401 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3402 goto error;
3403 }
3404 break;
3405
3406 case TCPCHK_EXPECT_HTTP_HEADER:
3407 if (!npat) {
3408 memprintf(errmsg, "unexpected error, undefined header name pattern");
3409 goto error;
3410 }
3411 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3412 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3413 if (!chk->expect.hdr.name_re)
3414 goto error;
3415 }
3416 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3417 px->conf.args.ctx = ARGC_SRV;
3418 LIST_INIT(&chk->expect.hdr.name_fmt);
3419 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3420 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3421 goto error;
3422 }
3423 }
3424 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003425 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003426 if (!isttest(chk->expect.hdr.name)) {
3427 memprintf(errmsg, "out of memory");
3428 goto error;
3429 }
3430 }
3431
3432 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3433 chk->expect.hdr.value = IST_NULL;
3434 break;
3435 }
3436
3437 if (!vpat) {
3438 memprintf(errmsg, "unexpected error, undefined header value pattern");
3439 goto error;
3440 }
3441 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3442 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3443 if (!chk->expect.hdr.value_re)
3444 goto error;
3445 }
3446 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3447 px->conf.args.ctx = ARGC_SRV;
3448 LIST_INIT(&chk->expect.hdr.value_fmt);
3449 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3450 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3451 goto error;
3452 }
3453 }
3454 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003455 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003456 if (!isttest(chk->expect.hdr.value)) {
3457 memprintf(errmsg, "out of memory");
3458 goto error;
3459 }
3460 }
3461
3462 break;
3463 case TCPCHK_EXPECT_CUSTOM:
3464 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3465 break;
3466 case TCPCHK_EXPECT_UNDEF:
3467 memprintf(errmsg, "pattern not found");
3468 goto error;
3469 }
3470
3471 /* All tcp-check expect points back to the first inverse expect rule in
3472 * a chain of one or more expect rule, potentially itself.
3473 */
3474 chk->expect.head = chk;
3475 list_for_each_entry_rev(prev_check, rules, list) {
3476 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3477 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3478 chk->expect.head = prev_check;
3479 continue;
3480 }
3481 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3482 break;
3483 }
3484 return chk;
3485
3486 error:
3487 free_tcpcheck(chk, 0);
3488 free(comment);
3489 release_sample_expr(status_expr);
3490 return NULL;
3491}
3492
3493/* Overwrites fields of the old http send rule with those of the new one. When
3494 * replaced, old values are freed and replaced by the new ones. New values are
3495 * not copied but transferred. At the end <new> should be empty and can be
3496 * safely released. This function never fails.
3497 */
3498void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3499{
3500 struct logformat_node *lf, *lfb;
3501 struct tcpcheck_http_hdr *hdr, *bhdr;
3502
3503
3504 if (new->send.http.meth.str.area) {
3505 free(old->send.http.meth.str.area);
3506 old->send.http.meth.meth = new->send.http.meth.meth;
3507 old->send.http.meth.str.area = new->send.http.meth.str.area;
3508 old->send.http.meth.str.data = new->send.http.meth.str.data;
3509 new->send.http.meth.str = BUF_NULL;
3510 }
3511
3512 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3513 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3514 istfree(&old->send.http.uri);
3515 else
3516 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3517 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3518 old->send.http.uri = new->send.http.uri;
3519 new->send.http.uri = IST_NULL;
3520 }
3521 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3522 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3523 istfree(&old->send.http.uri);
3524 else
3525 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3526 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3527 LIST_INIT(&old->send.http.uri_fmt);
3528 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003529 LIST_DELETE(&lf->list);
3530 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003531 }
3532 }
3533
3534 if (isttest(new->send.http.vsn)) {
3535 istfree(&old->send.http.vsn);
3536 old->send.http.vsn = new->send.http.vsn;
3537 new->send.http.vsn = IST_NULL;
3538 }
3539
3540 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3541 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003542 LIST_DELETE(&hdr->list);
3543 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003544 }
3545
3546 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3547 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3548 istfree(&old->send.http.body);
3549 else
3550 free_tcpcheck_fmt(&old->send.http.body_fmt);
3551 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3552 old->send.http.body = new->send.http.body;
3553 new->send.http.body = IST_NULL;
3554 }
3555 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3556 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3557 istfree(&old->send.http.body);
3558 else
3559 free_tcpcheck_fmt(&old->send.http.body_fmt);
3560 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3561 LIST_INIT(&old->send.http.body_fmt);
3562 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003563 LIST_DELETE(&lf->list);
3564 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003565 }
3566 }
3567}
3568
3569/* Internal function used to add an http-check rule in a list during the config
3570 * parsing step. Depending on its type, and the previously inserted rules, a
3571 * specific action may be performed or an error may be reported. This functions
3572 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3573 * message.
3574 */
3575int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3576{
3577 struct tcpcheck_rule *r;
3578
3579 /* the implicit send rule coming from an "option httpchk" line must be
3580 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003581 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003582 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003583 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003584 * sure the ruleset remains valid.
3585 */
3586
3587 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3588 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3589 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3590 * following tests are performed :
3591 *
3592 * 1- If there is no such rule or if it is not a send rule, the implicit send
3593 * rule is pushed in front of the ruleset
3594 *
3595 * 2- If it is another implicit send rule, it is replaced with the new one.
3596 *
3597 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3598 * both, overwriting the old send rule (the explicit one) with info of the
3599 * new send rule (the implicit one).
3600 */
3601 r = get_first_tcpcheck_rule(rules);
3602 if (r && r->action == TCPCHK_ACT_CONNECT)
3603 r = get_next_tcpcheck_rule(rules, r);
3604 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003605 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003606 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003607 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003608 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003609 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003610 }
3611 else {
3612 tcpcheck_overwrite_send_http_rule(r, chk);
3613 free_tcpcheck(chk, 0);
3614 }
3615 }
3616 else {
3617 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3618 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3619 * with an existing implicit send rule, if any. At the end, if there is no error,
3620 * the rule is appended to the list.
3621 */
3622
3623 r = get_last_tcpcheck_rule(rules);
3624 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3625 /* no error */;
3626 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3627 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3628 chk->index+1);
3629 return 0;
3630 }
3631 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3632 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3633 chk->index+1);
3634 return 0;
3635 }
3636 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3637 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3638 chk->index+1);
3639 return 0;
3640 }
3641
3642 if (chk->action == TCPCHK_ACT_SEND) {
3643 r = get_first_tcpcheck_rule(rules);
3644 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3645 tcpcheck_overwrite_send_http_rule(r, chk);
3646 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003647 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003648 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3649 chk = r;
3650 }
3651 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003652 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003653 }
3654 return 1;
3655}
3656
3657/* Check tcp-check health-check configuration for the proxy <px>. */
3658static int check_proxy_tcpcheck(struct proxy *px)
3659{
3660 struct tcpcheck_rule *chk, *back;
3661 char *comment = NULL, *errmsg = NULL;
3662 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003663 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003664
3665 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3666 deinit_proxy_tcpcheck(px);
3667 goto out;
3668 }
3669
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003670 ha_free(&px->check_command);
3671 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003672
3673 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003674 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003675 ret |= ERR_ALERT | ERR_FATAL;
3676 goto out;
3677 }
3678
3679 /* HTTP ruleset only : */
3680 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3681 struct tcpcheck_rule *next;
3682
3683 /* move remaining implicit send rule from "option httpchk" line to the right place.
3684 * If such rule exists, it must be the first one. In this case, the rule is moved
3685 * after the first connect rule, if any. Otherwise, nothing is done.
3686 */
3687 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3688 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3689 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3690 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003691 LIST_DELETE(&chk->list);
3692 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003693 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003694 }
3695 }
3696
3697 /* add implicit expect rule if the last one is a send. It is inherited from previous
3698 * versions where the http expect rule was optional. Now it is possible to chained
3699 * send/expect rules but the last expect may still be implicit.
3700 */
3701 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3702 if (chk && chk->action == TCPCHK_ACT_SEND) {
3703 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3704 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3705 px->conf.file, px->conf.line, &errmsg);
3706 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003707 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003708 "(%s).\n", px->id, errmsg);
3709 free(errmsg);
3710 ret |= ERR_ALERT | ERR_FATAL;
3711 goto out;
3712 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003713 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003714 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003715 }
3716 }
3717
3718 /* For all ruleset: */
3719
3720 /* If there is no connect rule preceding all send / expect rules, an
3721 * implicit one is inserted before all others.
3722 */
3723 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3724 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3725 chk = calloc(1, sizeof(*chk));
3726 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003727 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003728 "(out of memory).\n", px->id);
3729 ret |= ERR_ALERT | ERR_FATAL;
3730 goto out;
3731 }
3732 chk->action = TCPCHK_ACT_CONNECT;
3733 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003734 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003735 }
3736
3737 /* Remove all comment rules. To do so, when a such rule is found, the
3738 * comment is assigned to the following rule(s).
3739 */
3740 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003741 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3742 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003743
3744 prev_action = chk->action;
3745 switch (chk->action) {
3746 case TCPCHK_ACT_COMMENT:
3747 free(comment);
3748 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003749 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003750 free(chk);
3751 break;
3752 case TCPCHK_ACT_CONNECT:
3753 if (!chk->comment && comment)
3754 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003755 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003756 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003757 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003758 break;
3759 case TCPCHK_ACT_SEND:
3760 case TCPCHK_ACT_EXPECT:
3761 if (!chk->comment && comment)
3762 chk->comment = strdup(comment);
3763 break;
3764 }
3765 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003766 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003767
3768 out:
3769 return ret;
3770}
3771
3772void deinit_proxy_tcpcheck(struct proxy *px)
3773{
3774 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3775 px->tcpcheck_rules.flags = 0;
3776 px->tcpcheck_rules.list = NULL;
3777}
3778
3779static void deinit_tcpchecks()
3780{
3781 struct tcpcheck_ruleset *rs;
3782 struct tcpcheck_rule *r, *rb;
3783 struct ebpt_node *node, *next;
3784
3785 node = ebpt_first(&shared_tcpchecks);
3786 while (node) {
3787 next = ebpt_next(node);
3788 ebpt_delete(node);
3789 free(node->key);
3790 rs = container_of(node, typeof(*rs), node);
3791 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003792 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003793 free_tcpcheck(r, 0);
3794 }
3795 free(rs);
3796 node = next;
3797 }
3798}
3799
3800int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3801{
3802 struct tcpcheck_rule *tcpcheck, *prev_check;
3803 struct tcpcheck_expect *expect;
3804
Willy Tarreau6922e552021-03-22 21:11:45 +01003805 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003806 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003807 tcpcheck->action = TCPCHK_ACT_EXPECT;
3808
3809 expect = &tcpcheck->expect;
3810 expect->type = TCPCHK_EXPECT_STRING;
3811 LIST_INIT(&expect->onerror_fmt);
3812 LIST_INIT(&expect->onsuccess_fmt);
3813 expect->ok_status = HCHK_STATUS_L7OKD;
3814 expect->err_status = HCHK_STATUS_L7RSP;
3815 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003816 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003817 if (!isttest(expect->data)) {
3818 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3819 return 0;
3820 }
3821
3822 /* All tcp-check expect points back to the first inverse expect rule
3823 * in a chain of one or more expect rule, potentially itself.
3824 */
3825 tcpcheck->expect.head = tcpcheck;
3826 list_for_each_entry_rev(prev_check, rules->list, list) {
3827 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3828 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3829 tcpcheck->expect.head = prev_check;
3830 continue;
3831 }
3832 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3833 break;
3834 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003835 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003836 return 1;
3837}
3838
3839int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3840{
3841 struct tcpcheck_rule *tcpcheck;
3842 struct tcpcheck_send *send;
3843 const char *in;
3844 char *dst;
3845 int i;
3846
Willy Tarreau6922e552021-03-22 21:11:45 +01003847 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003848 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003849 tcpcheck->action = TCPCHK_ACT_SEND;
3850
3851 send = &tcpcheck->send;
3852 send->type = TCPCHK_SEND_STRING;
3853
3854 for (i = 0; strs[i]; i++)
3855 send->data.len += strlen(strs[i]);
3856
3857 send->data.ptr = malloc(istlen(send->data) + 1);
3858 if (!isttest(send->data)) {
3859 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3860 return 0;
3861 }
3862
3863 dst = istptr(send->data);
3864 for (i = 0; strs[i]; i++)
3865 for (in = strs[i]; (*dst = *in++); dst++);
3866 *dst = 0;
3867
Willy Tarreau2b718102021-04-21 07:32:39 +02003868 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003869 return 1;
3870}
3871
3872/* Parses the "tcp-check" proxy keyword */
3873static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003874 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003875 char **errmsg)
3876{
3877 struct tcpcheck_ruleset *rs = NULL;
3878 struct tcpcheck_rule *chk = NULL;
3879 int index, cur_arg, ret = 0;
3880
3881 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3882 ret = 1;
3883
3884 /* Deduce the ruleset name from the proxy info */
3885 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3886 ((curpx == defpx) ? "defaults" : curpx->id),
3887 curpx->conf.file, curpx->conf.line);
3888
3889 rs = find_tcpcheck_ruleset(b_orig(&trash));
3890 if (rs == NULL) {
3891 rs = create_tcpcheck_ruleset(b_orig(&trash));
3892 if (rs == NULL) {
3893 memprintf(errmsg, "out of memory.\n");
3894 goto error;
3895 }
3896 }
3897
3898 index = 0;
3899 if (!LIST_ISEMPTY(&rs->rules)) {
3900 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3901 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003902 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003903 }
3904
3905 cur_arg = 1;
3906 if (strcmp(args[cur_arg], "connect") == 0)
3907 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3908 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3909 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3910 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3911 else if (strcmp(args[cur_arg], "expect") == 0)
3912 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3913 else if (strcmp(args[cur_arg], "comment") == 0)
3914 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3915 else {
3916 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3917
3918 if (!kw) {
3919 action_kw_tcp_check_build_list(&trash);
3920 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3921 "%s%s. but got '%s'",
3922 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3923 goto error;
3924 }
3925 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3926 }
3927
3928 if (!chk) {
3929 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3930 goto error;
3931 }
3932 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3933
3934 /* No error: add the tcp-check rule in the list */
3935 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003936 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003937
3938 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3939 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3940 /* Use this ruleset if the proxy already has tcp-check enabled */
3941 curpx->tcpcheck_rules.list = &rs->rules;
3942 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3943 }
3944 else {
3945 /* mark this ruleset as unused for now */
3946 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3947 }
3948
3949 return ret;
3950
3951 error:
3952 free_tcpcheck(chk, 0);
3953 free_tcpcheck_ruleset(rs);
3954 return -1;
3955}
3956
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003957/* Parses the "http-check" proxy keyword */
3958static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003959 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003960 char **errmsg)
3961{
3962 struct tcpcheck_ruleset *rs = NULL;
3963 struct tcpcheck_rule *chk = NULL;
3964 int index, cur_arg, ret = 0;
3965
3966 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3967 ret = 1;
3968
3969 cur_arg = 1;
3970 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3971 /* enable a graceful server shutdown on an HTTP 404 response */
3972 curpx->options |= PR_O_DISABLE404;
3973 if (too_many_args(1, args, errmsg, NULL))
3974 goto error;
3975 goto out;
3976 }
3977 else if (strcmp(args[cur_arg], "send-state") == 0) {
3978 /* enable emission of the apparent state of a server in HTTP checks */
3979 curpx->options2 |= PR_O2_CHK_SNDST;
3980 if (too_many_args(1, args, errmsg, NULL))
3981 goto error;
3982 goto out;
3983 }
3984
3985 /* Deduce the ruleset name from the proxy info */
3986 chunk_printf(&trash, "*http-check-%s_%s-%d",
3987 ((curpx == defpx) ? "defaults" : curpx->id),
3988 curpx->conf.file, curpx->conf.line);
3989
3990 rs = find_tcpcheck_ruleset(b_orig(&trash));
3991 if (rs == NULL) {
3992 rs = create_tcpcheck_ruleset(b_orig(&trash));
3993 if (rs == NULL) {
3994 memprintf(errmsg, "out of memory.\n");
3995 goto error;
3996 }
3997 }
3998
3999 index = 0;
4000 if (!LIST_ISEMPTY(&rs->rules)) {
4001 chk = LIST_PREV(&rs->rules, typeof(chk), list);
4002 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4003 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004004 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004005 }
4006
4007 if (strcmp(args[cur_arg], "connect") == 0)
4008 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4009 else if (strcmp(args[cur_arg], "send") == 0)
4010 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4011 else if (strcmp(args[cur_arg], "expect") == 0)
4012 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4013 file, line, errmsg);
4014 else if (strcmp(args[cur_arg], "comment") == 0)
4015 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4016 else {
4017 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4018
4019 if (!kw) {
4020 action_kw_tcp_check_build_list(&trash);
4021 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4022 " 'send', 'expect'%s%s. but got '%s'",
4023 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4024 goto error;
4025 }
4026 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4027 }
4028
4029 if (!chk) {
4030 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4031 goto error;
4032 }
4033 ret = (*errmsg != NULL); /* Handle warning */
4034
4035 chk->index = index;
4036 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4037 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4038 /* Use this ruleset if the proxy already has http-check enabled */
4039 curpx->tcpcheck_rules.list = &rs->rules;
4040 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4041 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4042 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4043 curpx->tcpcheck_rules.list = NULL;
4044 goto error;
4045 }
4046 }
4047 else {
4048 /* mark this ruleset as unused for now */
4049 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004050 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004051 }
4052
4053 out:
4054 return ret;
4055
4056 error:
4057 free_tcpcheck(chk, 0);
4058 free_tcpcheck_ruleset(rs);
4059 return -1;
4060}
4061
4062/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004063int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004064 const char *file, int line)
4065{
4066 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4067 static char *redis_res = "+PONG\r\n";
4068
4069 struct tcpcheck_ruleset *rs = NULL;
4070 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4071 struct tcpcheck_rule *chk;
4072 char *errmsg = NULL;
4073 int err_code = 0;
4074
4075 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4076 err_code |= ERR_WARN;
4077
4078 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4079 goto out;
4080
4081 curpx->options2 &= ~PR_O2_CHK_ANY;
4082 curpx->options2 |= PR_O2_TCPCHK_CHK;
4083
4084 free_tcpcheck_vars(&rules->preset_vars);
4085 rules->list = NULL;
4086 rules->flags = 0;
4087
4088 rs = find_tcpcheck_ruleset("*redis-check");
4089 if (rs)
4090 goto ruleset_found;
4091
4092 rs = create_tcpcheck_ruleset("*redis-check");
4093 if (rs == NULL) {
4094 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4095 goto error;
4096 }
4097
4098 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4099 1, curpx, &rs->rules, file, line, &errmsg);
4100 if (!chk) {
4101 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4102 goto error;
4103 }
4104 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004105 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004106
4107 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4108 "error-status", "L7STS",
4109 "on-error", "%[res.payload(0,0),cut_crlf]",
4110 "on-success", "Redis server is ok",
4111 ""},
4112 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4113 if (!chk) {
4114 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4115 goto error;
4116 }
4117 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004118 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004119
4120 ruleset_found:
4121 rules->list = &rs->rules;
4122 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4123 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4124
4125 out:
4126 free(errmsg);
4127 return err_code;
4128
4129 error:
4130 free_tcpcheck_ruleset(rs);
4131 err_code |= ERR_ALERT | ERR_FATAL;
4132 goto out;
4133}
4134
4135
4136/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004137int 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 +01004138 const char *file, int line)
4139{
4140 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4141 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4142 *
4143 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4144 */
4145 static char sslv3_client_hello[] = {
4146 "16" /* ContentType : 0x16 = Handshake */
4147 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4148 "0079" /* ContentLength : 0x79 bytes after this one */
4149 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4150 "000075" /* HandshakeLength : 0x75 bytes after this one */
4151 "0300" /* Hello Version : 0x0300 = v3 */
4152 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4153 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4154 "00" /* Session ID length : empty (no session ID) */
4155 "004E" /* Cipher Suite Length : 78 bytes after this one */
4156 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4157 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4158 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4159 "000D" "000E" "000F" "0010" /* various bit lengths, */
4160 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4161 "0015" "0016" "0017" "0018"
4162 "0019" "001A" "001B" "002F"
4163 "0030" "0031" "0032" "0033"
4164 "0034" "0035" "0036" "0037"
4165 "0038" "0039" "003A"
4166 "01" /* Compression Length : 0x01 = 1 byte for types */
4167 "00" /* Compression Type : 0x00 = NULL compression */
4168 };
4169
4170 struct tcpcheck_ruleset *rs = NULL;
4171 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4172 struct tcpcheck_rule *chk;
4173 char *errmsg = NULL;
4174 int err_code = 0;
4175
4176 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4177 err_code |= ERR_WARN;
4178
4179 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4180 goto out;
4181
4182 curpx->options2 &= ~PR_O2_CHK_ANY;
4183 curpx->options2 |= PR_O2_TCPCHK_CHK;
4184
4185 free_tcpcheck_vars(&rules->preset_vars);
4186 rules->list = NULL;
4187 rules->flags = 0;
4188
4189 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4190 if (rs)
4191 goto ruleset_found;
4192
4193 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4194 if (rs == NULL) {
4195 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4196 goto error;
4197 }
4198
4199 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4200 1, curpx, &rs->rules, file, line, &errmsg);
4201 if (!chk) {
4202 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4203 goto error;
4204 }
4205 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004206 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004207
4208 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4209 "min-recv", "5", "ok-status", "L6OK",
4210 "error-status", "L6RSP", "tout-status", "L6TOUT",
4211 ""},
4212 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4213 if (!chk) {
4214 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4215 goto error;
4216 }
4217 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004218 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004219
4220 ruleset_found:
4221 rules->list = &rs->rules;
4222 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4223 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4224
4225 out:
4226 free(errmsg);
4227 return err_code;
4228
4229 error:
4230 free_tcpcheck_ruleset(rs);
4231 err_code |= ERR_ALERT | ERR_FATAL;
4232 goto out;
4233}
4234
4235/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004236int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004237 const char *file, int line)
4238{
4239 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4240
4241 struct tcpcheck_ruleset *rs = NULL;
4242 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4243 struct tcpcheck_rule *chk;
4244 struct tcpcheck_var *var = NULL;
4245 char *cmd = NULL, *errmsg = NULL;
4246 int err_code = 0;
4247
4248 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4249 err_code |= ERR_WARN;
4250
4251 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4252 goto out;
4253
4254 curpx->options2 &= ~PR_O2_CHK_ANY;
4255 curpx->options2 |= PR_O2_TCPCHK_CHK;
4256
4257 free_tcpcheck_vars(&rules->preset_vars);
4258 rules->list = NULL;
4259 rules->flags = 0;
4260
4261 cur_arg += 2;
4262 if (*args[cur_arg] && *args[cur_arg+1] &&
4263 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4264 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4265 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4266 if (cmd)
4267 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4268 }
4269 else {
4270 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4271 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4272 cmd = strdup("HELO localhost");
4273 }
4274
4275 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4276 if (cmd == NULL || var == NULL) {
4277 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4278 goto error;
4279 }
4280 var->data.type = SMP_T_STR;
4281 var->data.u.str.area = cmd;
4282 var->data.u.str.data = strlen(cmd);
4283 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004284 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004285 cmd = NULL;
4286 var = NULL;
4287
4288 rs = find_tcpcheck_ruleset("*smtp-check");
4289 if (rs)
4290 goto ruleset_found;
4291
4292 rs = create_tcpcheck_ruleset("*smtp-check");
4293 if (rs == NULL) {
4294 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4295 goto error;
4296 }
4297
4298 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4299 1, curpx, &rs->rules, file, line, &errmsg);
4300 if (!chk) {
4301 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4302 goto error;
4303 }
4304 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004305 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004306
4307 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4308 "min-recv", "4",
4309 "error-status", "L7RSP",
4310 "on-error", "%[res.payload(0,0),cut_crlf]",
4311 ""},
4312 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4313 if (!chk) {
4314 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4315 goto error;
4316 }
4317 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004318 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004319
4320 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4321 "min-recv", "4",
4322 "error-status", "L7STS",
4323 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4324 "status-code", "res.payload(0,3)",
4325 ""},
4326 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4327 if (!chk) {
4328 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4329 goto error;
4330 }
4331 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004332 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004333
4334 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4335 1, curpx, &rs->rules, file, line, &errmsg);
4336 if (!chk) {
4337 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4338 goto error;
4339 }
4340 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004341 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004342
4343 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4344 "min-recv", "4",
4345 "error-status", "L7STS",
4346 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4347 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4348 "status-code", "res.payload(0,3)",
4349 ""},
4350 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4351 if (!chk) {
4352 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4353 goto error;
4354 }
4355 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004356 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004357
4358 ruleset_found:
4359 rules->list = &rs->rules;
4360 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4361 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4362
4363 out:
4364 free(errmsg);
4365 return err_code;
4366
4367 error:
4368 free(cmd);
4369 free(var);
4370 free_tcpcheck_vars(&rules->preset_vars);
4371 free_tcpcheck_ruleset(rs);
4372 err_code |= ERR_ALERT | ERR_FATAL;
4373 goto out;
4374}
4375
4376/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004377int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004378 const char *file, int line)
4379{
4380 static char pgsql_req[] = {
4381 "%[var(check.plen),htonl,hex]" /* The packet length*/
4382 "00030000" /* the version 3.0 */
4383 "7573657200" /* "user" key */
4384 "%[var(check.username),hex]00" /* the username */
4385 "00"
4386 };
4387
4388 struct tcpcheck_ruleset *rs = NULL;
4389 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4390 struct tcpcheck_rule *chk;
4391 struct tcpcheck_var *var = NULL;
4392 char *user = NULL, *errmsg = NULL;
4393 size_t packetlen = 0;
4394 int err_code = 0;
4395
4396 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4397 err_code |= ERR_WARN;
4398
4399 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4400 goto out;
4401
4402 curpx->options2 &= ~PR_O2_CHK_ANY;
4403 curpx->options2 |= PR_O2_TCPCHK_CHK;
4404
4405 free_tcpcheck_vars(&rules->preset_vars);
4406 rules->list = NULL;
4407 rules->flags = 0;
4408
4409 cur_arg += 2;
4410 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4411 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4412 file, line, args[0], args[1]);
4413 goto error;
4414 }
4415 if (strcmp(args[cur_arg], "user") == 0) {
4416 packetlen = 15 + strlen(args[cur_arg+1]);
4417 user = strdup(args[cur_arg+1]);
4418
4419 var = create_tcpcheck_var(ist("check.username"));
4420 if (user == NULL || var == NULL) {
4421 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4422 goto error;
4423 }
4424 var->data.type = SMP_T_STR;
4425 var->data.u.str.area = user;
4426 var->data.u.str.data = strlen(user);
4427 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004428 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004429 user = NULL;
4430 var = NULL;
4431
4432 var = create_tcpcheck_var(ist("check.plen"));
4433 if (var == NULL) {
4434 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4435 goto error;
4436 }
4437 var->data.type = SMP_T_SINT;
4438 var->data.u.sint = packetlen;
4439 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004440 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004441 var = NULL;
4442 }
4443 else {
4444 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4445 file, line, args[0], args[1]);
4446 goto error;
4447 }
4448
4449 rs = find_tcpcheck_ruleset("*pgsql-check");
4450 if (rs)
4451 goto ruleset_found;
4452
4453 rs = create_tcpcheck_ruleset("*pgsql-check");
4454 if (rs == NULL) {
4455 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4456 goto error;
4457 }
4458
4459 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4460 1, curpx, &rs->rules, file, line, &errmsg);
4461 if (!chk) {
4462 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4463 goto error;
4464 }
4465 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004466 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004467
4468 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4469 1, curpx, &rs->rules, file, line, &errmsg);
4470 if (!chk) {
4471 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4472 goto error;
4473 }
4474 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004475 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004476
4477 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4478 "min-recv", "5",
4479 "error-status", "L7RSP",
4480 "on-error", "%[res.payload(6,0)]",
4481 ""},
4482 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4483 if (!chk) {
4484 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4485 goto error;
4486 }
4487 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004488 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004489
4490 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4491 "min-recv", "9",
4492 "error-status", "L7STS",
4493 "on-success", "PostgreSQL server is ok",
4494 "on-error", "PostgreSQL unknown error",
4495 ""},
4496 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4497 if (!chk) {
4498 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4499 goto error;
4500 }
4501 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004502 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004503
4504 ruleset_found:
4505 rules->list = &rs->rules;
4506 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4507 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4508
4509 out:
4510 free(errmsg);
4511 return err_code;
4512
4513 error:
4514 free(user);
4515 free(var);
4516 free_tcpcheck_vars(&rules->preset_vars);
4517 free_tcpcheck_ruleset(rs);
4518 err_code |= ERR_ALERT | ERR_FATAL;
4519 goto out;
4520}
4521
4522
4523/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004524int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004525 const char *file, int line)
4526{
4527 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4528 * const char mysql40_client_auth_pkt[] = {
4529 * "\x0e\x00\x00" // packet length
4530 * "\x01" // packet number
4531 * "\x00\x00" // client capabilities
4532 * "\x00\x00\x01" // max packet
4533 * "haproxy\x00" // username (null terminated string)
4534 * "\x00" // filler (always 0x00)
4535 * "\x01\x00\x00" // packet length
4536 * "\x00" // packet number
4537 * "\x01" // COM_QUIT command
4538 * };
4539 */
4540 static char mysql40_rsname[] = "*mysql40-check";
4541 static char mysql40_req[] = {
4542 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4543 "0080" /* client capabilities */
4544 "000001" /* max packet */
4545 "%[var(check.username),hex]00" /* the username */
4546 "00" /* filler (always 0x00) */
4547 "010000" /* packet length*/
4548 "00" /* sequence ID */
4549 "01" /* COM_QUIT command */
4550 };
4551
4552 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4553 * const char mysql41_client_auth_pkt[] = {
4554 * "\x0e\x00\x00\" // packet length
4555 * "\x01" // packet number
4556 * "\x00\x00\x00\x00" // client capabilities
4557 * "\x00\x00\x00\x01" // max packet
4558 * "\x21" // character set (UTF-8)
4559 * char[23] // All zeroes
4560 * "haproxy\x00" // username (null terminated string)
4561 * "\x00" // filler (always 0x00)
4562 * "\x01\x00\x00" // packet length
4563 * "\x00" // packet number
4564 * "\x01" // COM_QUIT command
4565 * };
4566 */
4567 static char mysql41_rsname[] = "*mysql41-check";
4568 static char mysql41_req[] = {
4569 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4570 "00820000" /* client capabilities */
4571 "00800001" /* max packet */
4572 "21" /* character set (UTF-8) */
4573 "000000000000000000000000" /* 23 bytes, al zeroes */
4574 "0000000000000000000000"
4575 "%[var(check.username),hex]00" /* the username */
4576 "00" /* filler (always 0x00) */
4577 "010000" /* packet length*/
4578 "00" /* sequence ID */
4579 "01" /* COM_QUIT command */
4580 };
4581
4582 struct tcpcheck_ruleset *rs = NULL;
4583 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4584 struct tcpcheck_rule *chk;
4585 struct tcpcheck_var *var = NULL;
4586 char *mysql_rsname = "*mysql-check";
4587 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4588 int index = 0, err_code = 0;
4589
4590 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4591 err_code |= ERR_WARN;
4592
4593 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4594 goto out;
4595
4596 curpx->options2 &= ~PR_O2_CHK_ANY;
4597 curpx->options2 |= PR_O2_TCPCHK_CHK;
4598
4599 free_tcpcheck_vars(&rules->preset_vars);
4600 rules->list = NULL;
4601 rules->flags = 0;
4602
4603 cur_arg += 2;
4604 if (*args[cur_arg]) {
4605 int packetlen, userlen;
4606
4607 if (strcmp(args[cur_arg], "user") != 0) {
4608 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4609 file, line, args[0], args[1], args[cur_arg]);
4610 goto error;
4611 }
4612
4613 if (*(args[cur_arg+1]) == 0) {
4614 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4615 file, line, args[0], args[1], args[cur_arg]);
4616 goto error;
4617 }
4618
4619 hdr = calloc(4, sizeof(*hdr));
4620 user = strdup(args[cur_arg+1]);
4621 userlen = strlen(args[cur_arg+1]);
4622
4623 if (hdr == NULL || user == NULL) {
4624 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4625 goto error;
4626 }
4627
4628 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4629 packetlen = userlen + 7 + 27;
4630 mysql_req = mysql41_req;
4631 mysql_rsname = mysql41_rsname;
4632 }
4633 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4634 packetlen = userlen + 7;
4635 mysql_req = mysql40_req;
4636 mysql_rsname = mysql40_rsname;
4637 }
4638 else {
4639 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4640 file, line, args[cur_arg], args[cur_arg+2]);
4641 goto error;
4642 }
4643
4644 hdr[0] = (unsigned char)(packetlen & 0xff);
4645 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4646 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4647 hdr[3] = 1;
4648
4649 var = create_tcpcheck_var(ist("check.header"));
4650 if (var == NULL) {
4651 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4652 goto error;
4653 }
4654 var->data.type = SMP_T_STR;
4655 var->data.u.str.area = hdr;
4656 var->data.u.str.data = 4;
4657 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004658 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004659 hdr = NULL;
4660 var = NULL;
4661
4662 var = create_tcpcheck_var(ist("check.username"));
4663 if (var == NULL) {
4664 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4665 goto error;
4666 }
4667 var->data.type = SMP_T_STR;
4668 var->data.u.str.area = user;
4669 var->data.u.str.data = strlen(user);
4670 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004671 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004672 user = NULL;
4673 var = NULL;
4674 }
4675
4676 rs = find_tcpcheck_ruleset(mysql_rsname);
4677 if (rs)
4678 goto ruleset_found;
4679
4680 rs = create_tcpcheck_ruleset(mysql_rsname);
4681 if (rs == NULL) {
4682 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4683 goto error;
4684 }
4685
4686 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4687 1, curpx, &rs->rules, file, line, &errmsg);
4688 if (!chk) {
4689 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4690 goto error;
4691 }
4692 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004693 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004694
4695 if (mysql_req) {
4696 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4697 1, curpx, &rs->rules, file, line, &errmsg);
4698 if (!chk) {
4699 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4700 goto error;
4701 }
4702 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004703 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004704 }
4705
4706 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4707 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4708 if (!chk) {
4709 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4710 goto error;
4711 }
4712 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4713 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004714 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004715
4716 if (mysql_req) {
4717 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4718 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4719 if (!chk) {
4720 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4721 goto error;
4722 }
4723 chk->expect.custom = tcpcheck_mysql_expect_ok;
4724 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004725 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004726 }
4727
4728 ruleset_found:
4729 rules->list = &rs->rules;
4730 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4731 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4732
4733 out:
4734 free(errmsg);
4735 return err_code;
4736
4737 error:
4738 free(hdr);
4739 free(user);
4740 free(var);
4741 free_tcpcheck_vars(&rules->preset_vars);
4742 free_tcpcheck_ruleset(rs);
4743 err_code |= ERR_ALERT | ERR_FATAL;
4744 goto out;
4745}
4746
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004747int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004748 const char *file, int line)
4749{
4750 static char *ldap_req = "300C020101600702010304008000";
4751
4752 struct tcpcheck_ruleset *rs = NULL;
4753 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4754 struct tcpcheck_rule *chk;
4755 char *errmsg = NULL;
4756 int err_code = 0;
4757
4758 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4759 err_code |= ERR_WARN;
4760
4761 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4762 goto out;
4763
4764 curpx->options2 &= ~PR_O2_CHK_ANY;
4765 curpx->options2 |= PR_O2_TCPCHK_CHK;
4766
4767 free_tcpcheck_vars(&rules->preset_vars);
4768 rules->list = NULL;
4769 rules->flags = 0;
4770
4771 rs = find_tcpcheck_ruleset("*ldap-check");
4772 if (rs)
4773 goto ruleset_found;
4774
4775 rs = create_tcpcheck_ruleset("*ldap-check");
4776 if (rs == NULL) {
4777 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4778 goto error;
4779 }
4780
4781 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4782 1, curpx, &rs->rules, file, line, &errmsg);
4783 if (!chk) {
4784 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4785 goto error;
4786 }
4787 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004788 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004789
4790 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4791 "min-recv", "14",
4792 "on-error", "Not LDAPv3 protocol",
4793 ""},
4794 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4795 if (!chk) {
4796 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4797 goto error;
4798 }
4799 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004800 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004801
4802 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4803 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4804 if (!chk) {
4805 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4806 goto error;
4807 }
4808 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4809 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004810 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004811
4812 ruleset_found:
4813 rules->list = &rs->rules;
4814 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4815 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4816
4817 out:
4818 free(errmsg);
4819 return err_code;
4820
4821 error:
4822 free_tcpcheck_ruleset(rs);
4823 err_code |= ERR_ALERT | ERR_FATAL;
4824 goto out;
4825}
4826
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004827int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004828 const char *file, int line)
4829{
4830 struct tcpcheck_ruleset *rs = NULL;
4831 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4832 struct tcpcheck_rule *chk;
4833 char *spop_req = NULL;
4834 char *errmsg = NULL;
4835 int spop_len = 0, err_code = 0;
4836
4837 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4838 err_code |= ERR_WARN;
4839
4840 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4841 goto out;
4842
4843 curpx->options2 &= ~PR_O2_CHK_ANY;
4844 curpx->options2 |= PR_O2_TCPCHK_CHK;
4845
4846 free_tcpcheck_vars(&rules->preset_vars);
4847 rules->list = NULL;
4848 rules->flags = 0;
4849
4850
4851 rs = find_tcpcheck_ruleset("*spop-check");
4852 if (rs)
4853 goto ruleset_found;
4854
4855 rs = create_tcpcheck_ruleset("*spop-check");
4856 if (rs == NULL) {
4857 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4858 goto error;
4859 }
4860
4861 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4862 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4863 goto error;
4864 }
4865 chunk_reset(&trash);
4866 dump_binary(&trash, spop_req, spop_len);
4867 trash.area[trash.data] = '\0';
4868
4869 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4870 1, curpx, &rs->rules, file, line, &errmsg);
4871 if (!chk) {
4872 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4873 goto error;
4874 }
4875 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004876 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004877
4878 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4879 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4880 if (!chk) {
4881 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4882 goto error;
4883 }
4884 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4885 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004886 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004887
4888 ruleset_found:
4889 rules->list = &rs->rules;
4890 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4891 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4892
4893 out:
4894 free(spop_req);
4895 free(errmsg);
4896 return err_code;
4897
4898 error:
4899 free_tcpcheck_ruleset(rs);
4900 err_code |= ERR_ALERT | ERR_FATAL;
4901 goto out;
4902}
4903
4904
4905static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4906{
4907 struct tcpcheck_rule *chk = NULL;
4908 struct tcpcheck_http_hdr *hdr = NULL;
4909 char *meth = NULL, *uri = NULL, *vsn = NULL;
4910 char *hdrs, *body;
4911
4912 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4913 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4914 if (hdrs == body)
4915 hdrs = NULL;
4916 if (hdrs) {
4917 *hdrs = '\0';
4918 hdrs +=2;
4919 }
4920 if (body) {
4921 *body = '\0';
4922 body += 4;
4923 }
4924 if (hdrs || body) {
4925 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4926 " Please, consider to use 'http-check send' directive instead.");
4927 }
4928
4929 chk = calloc(1, sizeof(*chk));
4930 if (!chk) {
4931 memprintf(errmsg, "out of memory");
4932 goto error;
4933 }
4934 chk->action = TCPCHK_ACT_SEND;
4935 chk->send.type = TCPCHK_SEND_HTTP;
4936 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4937 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4938 LIST_INIT(&chk->send.http.hdrs);
4939
4940 /* Copy the method, uri and version */
4941 if (*args[cur_arg]) {
4942 if (!*args[cur_arg+1])
4943 uri = args[cur_arg];
4944 else
4945 meth = args[cur_arg];
4946 }
4947 if (*args[cur_arg+1])
4948 uri = args[cur_arg+1];
4949 if (*args[cur_arg+2])
4950 vsn = args[cur_arg+2];
4951
4952 if (meth) {
4953 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4954 chk->send.http.meth.str.area = strdup(meth);
4955 chk->send.http.meth.str.data = strlen(meth);
4956 if (!chk->send.http.meth.str.area) {
4957 memprintf(errmsg, "out of memory");
4958 goto error;
4959 }
4960 }
4961 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004962 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004963 if (!isttest(chk->send.http.uri)) {
4964 memprintf(errmsg, "out of memory");
4965 goto error;
4966 }
4967 }
4968 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004969 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004970 if (!isttest(chk->send.http.vsn)) {
4971 memprintf(errmsg, "out of memory");
4972 goto error;
4973 }
4974 }
4975
4976 /* Copy the header */
4977 if (hdrs) {
4978 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4979 struct h1m h1m;
4980 int i, ret;
4981
4982 /* Build and parse the request */
4983 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4984
4985 h1m.flags = H1_MF_HDRS_ONLY;
4986 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4987 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4988 &h1m, NULL);
4989 if (ret <= 0) {
4990 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4991 goto error;
4992 }
4993
4994 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4995 hdr = calloc(1, sizeof(*hdr));
4996 if (!hdr) {
4997 memprintf(errmsg, "out of memory");
4998 goto error;
4999 }
5000 LIST_INIT(&hdr->value);
5001 hdr->name = istdup(tmp_hdrs[i].n);
Tim Duesterhus77508502022-03-15 13:11:06 +01005002 if (!isttest(hdr->name)) {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005003 memprintf(errmsg, "out of memory");
5004 goto error;
5005 }
5006
5007 ist0(tmp_hdrs[i].v);
5008 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5009 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005010 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005011 }
5012 }
5013
5014 /* Copy the body */
5015 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005016 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005017 if (!isttest(chk->send.http.body)) {
5018 memprintf(errmsg, "out of memory");
5019 goto error;
5020 }
5021 }
5022
5023 return chk;
5024
5025 error:
5026 free_tcpcheck_http_hdr(hdr);
5027 free_tcpcheck(chk, 0);
5028 return NULL;
5029}
5030
5031/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005032int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005033 const char *file, int line)
5034{
5035 struct tcpcheck_ruleset *rs = NULL;
5036 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5037 struct tcpcheck_rule *chk;
5038 char *errmsg = NULL;
5039 int err_code = 0;
5040
5041 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5042 err_code |= ERR_WARN;
5043
5044 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5045 goto out;
5046
5047 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5048 if (!chk) {
5049 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5050 goto error;
5051 }
5052 if (errmsg) {
5053 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5054 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005055 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005056 }
5057
5058 no_request:
5059 curpx->options2 &= ~PR_O2_CHK_ANY;
5060 curpx->options2 |= PR_O2_TCPCHK_CHK;
5061
5062 free_tcpcheck_vars(&rules->preset_vars);
5063 rules->list = NULL;
5064 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5065
5066 /* Deduce the ruleset name from the proxy info */
5067 chunk_printf(&trash, "*http-check-%s_%s-%d",
5068 ((curpx == defpx) ? "defaults" : curpx->id),
5069 curpx->conf.file, curpx->conf.line);
5070
5071 rs = find_tcpcheck_ruleset(b_orig(&trash));
5072 if (rs == NULL) {
5073 rs = create_tcpcheck_ruleset(b_orig(&trash));
5074 if (rs == NULL) {
5075 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5076 goto error;
5077 }
5078 }
5079
5080 rules->list = &rs->rules;
5081 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5082 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5083 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5084 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5085 rules->list = NULL;
5086 goto error;
5087 }
5088
5089 out:
5090 free(errmsg);
5091 return err_code;
5092
5093 error:
5094 free_tcpcheck_ruleset(rs);
5095 free_tcpcheck(chk, 0);
5096 err_code |= ERR_ALERT | ERR_FATAL;
5097 goto out;
5098}
5099
5100/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005101int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005102 const char *file, int line)
5103{
5104 struct tcpcheck_ruleset *rs = NULL;
5105 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5106 int err_code = 0;
5107
5108 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5109 err_code |= ERR_WARN;
5110
5111 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5112 goto out;
5113
5114 curpx->options2 &= ~PR_O2_CHK_ANY;
5115 curpx->options2 |= PR_O2_TCPCHK_CHK;
5116
5117 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5118 /* If a tcp-check rulesset is already set, do nothing */
5119 if (rules->list)
5120 goto out;
5121
5122 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5123 * get it.
5124 */
5125 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5126 goto curpx_ruleset;
5127
5128 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5129 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5130 rs = find_tcpcheck_ruleset(b_orig(&trash));
5131 if (rs)
5132 goto ruleset_found;
5133 }
5134
5135 curpx_ruleset:
5136 /* Deduce the ruleset name from the proxy info */
5137 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5138 ((curpx == defpx) ? "defaults" : curpx->id),
5139 curpx->conf.file, curpx->conf.line);
5140
5141 rs = find_tcpcheck_ruleset(b_orig(&trash));
5142 if (rs == NULL) {
5143 rs = create_tcpcheck_ruleset(b_orig(&trash));
5144 if (rs == NULL) {
5145 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5146 goto error;
5147 }
5148 }
5149
5150 ruleset_found:
5151 free_tcpcheck_vars(&rules->preset_vars);
5152 rules->list = &rs->rules;
5153 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5154 rules->flags |= TCPCHK_RULES_TCP_CHK;
5155
5156 out:
5157 return err_code;
5158
5159 error:
5160 err_code |= ERR_ALERT | ERR_FATAL;
5161 goto out;
5162}
5163
Willy Tarreau51cd5952020-06-05 12:25:38 +02005164static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005165 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005166 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5167 { 0, NULL, NULL },
5168}};
5169
5170REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5171REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5172REGISTER_POST_DEINIT(deinit_tcpchecks);
5173INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);