blob: df420b5d8f3c4055eea0f87acbced2a8dfaa294c [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>
Willy Tarreau51cd5952020-06-05 12:25:38 +020027#include <signal.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <time.h>
33#include <unistd.h>
34
35#include <haproxy/action.h>
36#include <haproxy/api.h>
37#include <haproxy/cfgparse.h>
38#include <haproxy/check.h>
39#include <haproxy/chunk.h>
40#include <haproxy/connection.h>
Christopher Faulet1329f2a2021-12-16 17:32:56 +010041#include <haproxy/conn_stream.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020042#include <haproxy/errors.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020043#include <haproxy/global.h>
44#include <haproxy/h1.h>
45#include <haproxy/http.h>
46#include <haproxy/http_htx.h>
47#include <haproxy/htx.h>
48#include <haproxy/istbuf.h>
49#include <haproxy/list.h>
50#include <haproxy/log.h>
Christopher Faulet8a0e5f82021-09-16 16:01:09 +020051#include <haproxy/net_helper.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020052#include <haproxy/protocol.h>
53#include <haproxy/proxy-t.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020054#include <haproxy/regex.h>
55#include <haproxy/sample.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020056#include <haproxy/server.h>
57#include <haproxy/ssl_sock.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020058#include <haproxy/task.h>
59#include <haproxy/tcpcheck.h>
Willy Tarreau9310f482021-10-06 16:18:40 +020060#include <haproxy/ticks.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020061#include <haproxy/tools.h>
Christopher Faulet147b8c92021-04-10 09:00:38 +020062#include <haproxy/trace.h>
Willy Tarreau51cd5952020-06-05 12:25:38 +020063#include <haproxy/vars.h>
64
65
Christopher Faulet147b8c92021-04-10 09:00:38 +020066#define TRACE_SOURCE &trace_check
67
Willy Tarreau51cd5952020-06-05 12:25:38 +020068/* Global tree to share all tcp-checks */
69struct eb_root shared_tcpchecks = EB_ROOT;
70
71
72DECLARE_POOL(pool_head_tcpcheck_rule, "tcpcheck_rule", sizeof(struct tcpcheck_rule));
73
74/**************************************************************************/
75/*************** Init/deinit tcp-check rules and ruleset ******************/
76/**************************************************************************/
77/* Releases memory allocated for a log-format string */
78static void free_tcpcheck_fmt(struct list *fmt)
79{
80 struct logformat_node *lf, *lfb;
81
82 list_for_each_entry_safe(lf, lfb, fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +020083 LIST_DELETE(&lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +020084 release_sample_expr(lf->expr);
85 free(lf->arg);
86 free(lf);
87 }
88}
89
90/* Releases memory allocated for an HTTP header used in a tcp-check send rule */
91void free_tcpcheck_http_hdr(struct tcpcheck_http_hdr *hdr)
92{
93 if (!hdr)
94 return;
95
96 free_tcpcheck_fmt(&hdr->value);
97 istfree(&hdr->name);
98 free(hdr);
99}
100
101/* Releases memory allocated for an HTTP header list used in a tcp-check send
102 * rule
103 */
104static void free_tcpcheck_http_hdrs(struct list *hdrs)
105{
106 struct tcpcheck_http_hdr *hdr, *bhdr;
107
108 list_for_each_entry_safe(hdr, bhdr, hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200109 LIST_DELETE(&hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200110 free_tcpcheck_http_hdr(hdr);
111 }
112}
113
114/* Releases memory allocated for a tcp-check. If in_pool is set, it means the
115 * tcp-check was allocated using a memory pool (it is used to instantiate email
116 * alerts).
117 */
118void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool)
119{
120 if (!rule)
121 return;
122
123 free(rule->comment);
124 switch (rule->action) {
125 case TCPCHK_ACT_SEND:
126 switch (rule->send.type) {
127 case TCPCHK_SEND_STRING:
128 case TCPCHK_SEND_BINARY:
129 istfree(&rule->send.data);
130 break;
131 case TCPCHK_SEND_STRING_LF:
132 case TCPCHK_SEND_BINARY_LF:
133 free_tcpcheck_fmt(&rule->send.fmt);
134 break;
135 case TCPCHK_SEND_HTTP:
136 free(rule->send.http.meth.str.area);
137 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
138 istfree(&rule->send.http.uri);
139 else
140 free_tcpcheck_fmt(&rule->send.http.uri_fmt);
141 istfree(&rule->send.http.vsn);
142 free_tcpcheck_http_hdrs(&rule->send.http.hdrs);
143 if (!(rule->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
144 istfree(&rule->send.http.body);
145 else
146 free_tcpcheck_fmt(&rule->send.http.body_fmt);
147 break;
148 case TCPCHK_SEND_UNDEF:
149 break;
150 }
151 break;
152 case TCPCHK_ACT_EXPECT:
153 free_tcpcheck_fmt(&rule->expect.onerror_fmt);
154 free_tcpcheck_fmt(&rule->expect.onsuccess_fmt);
155 release_sample_expr(rule->expect.status_expr);
156 switch (rule->expect.type) {
157 case TCPCHK_EXPECT_HTTP_STATUS:
158 free(rule->expect.codes.codes);
159 break;
160 case TCPCHK_EXPECT_STRING:
161 case TCPCHK_EXPECT_BINARY:
162 case TCPCHK_EXPECT_HTTP_BODY:
163 istfree(&rule->expect.data);
164 break;
165 case TCPCHK_EXPECT_STRING_REGEX:
166 case TCPCHK_EXPECT_BINARY_REGEX:
167 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
168 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
169 regex_free(rule->expect.regex);
170 break;
171 case TCPCHK_EXPECT_STRING_LF:
172 case TCPCHK_EXPECT_BINARY_LF:
173 case TCPCHK_EXPECT_HTTP_BODY_LF:
174 free_tcpcheck_fmt(&rule->expect.fmt);
175 break;
176 case TCPCHK_EXPECT_HTTP_HEADER:
177 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG)
178 regex_free(rule->expect.hdr.name_re);
179 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT)
180 free_tcpcheck_fmt(&rule->expect.hdr.name_fmt);
181 else
182 istfree(&rule->expect.hdr.name);
183
184 if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG)
185 regex_free(rule->expect.hdr.value_re);
186 else if (rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT)
187 free_tcpcheck_fmt(&rule->expect.hdr.value_fmt);
188 else if (!(rule->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE))
189 istfree(&rule->expect.hdr.value);
190 break;
191 case TCPCHK_EXPECT_CUSTOM:
192 case TCPCHK_EXPECT_UNDEF:
193 break;
194 }
195 break;
196 case TCPCHK_ACT_CONNECT:
197 free(rule->connect.sni);
198 free(rule->connect.alpn);
199 release_sample_expr(rule->connect.port_expr);
200 break;
201 case TCPCHK_ACT_COMMENT:
202 break;
203 case TCPCHK_ACT_ACTION_KW:
204 free(rule->action_kw.rule);
205 break;
206 }
207
208 if (in_pool)
209 pool_free(pool_head_tcpcheck_rule, rule);
210 else
211 free(rule);
212}
213
214/* Creates a tcp-check variable used in preset variables before executing a
215 * tcp-check ruleset.
216 */
217struct tcpcheck_var *create_tcpcheck_var(const struct ist name)
218{
219 struct tcpcheck_var *var = NULL;
220
221 var = calloc(1, sizeof(*var));
222 if (var == NULL)
223 return NULL;
224
225 var->name = istdup(name);
226 if (!isttest(var->name)) {
227 free(var);
228 return NULL;
229 }
230
231 LIST_INIT(&var->list);
232 return var;
233}
234
235/* Releases memory allocated for a preset tcp-check variable */
236void free_tcpcheck_var(struct tcpcheck_var *var)
237{
238 if (!var)
239 return;
240
241 istfree(&var->name);
242 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN)
243 free(var->data.u.str.area);
244 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER)
245 free(var->data.u.meth.str.area);
246 free(var);
247}
248
249/* Releases a list of preset tcp-check variables */
250void free_tcpcheck_vars(struct list *vars)
251{
252 struct tcpcheck_var *var, *back;
253
254 list_for_each_entry_safe(var, back, vars, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200255 LIST_DELETE(&var->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200256 free_tcpcheck_var(var);
257 }
258}
259
260/* Duplicate a list of preset tcp-check variables */
Willy Tarreau09f2e772021-02-12 08:42:30 +0100261int dup_tcpcheck_vars(struct list *dst, const struct list *src)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200262{
Willy Tarreau09f2e772021-02-12 08:42:30 +0100263 const struct tcpcheck_var *var;
264 struct tcpcheck_var *new = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200265
266 list_for_each_entry(var, src, list) {
267 new = create_tcpcheck_var(var->name);
268 if (!new)
269 goto error;
270 new->data.type = var->data.type;
271 if (var->data.type == SMP_T_STR || var->data.type == SMP_T_BIN) {
272 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
273 goto error;
274 if (var->data.type == SMP_T_STR)
275 new->data.u.str.area[new->data.u.str.data] = 0;
276 }
277 else if (var->data.type == SMP_T_METH && var->data.u.meth.meth == HTTP_METH_OTHER) {
278 if (chunk_dup(&new->data.u.str, &var->data.u.str) == NULL)
279 goto error;
280 new->data.u.str.area[new->data.u.str.data] = 0;
281 new->data.u.meth.meth = var->data.u.meth.meth;
282 }
283 else
284 new->data.u = var->data.u;
Willy Tarreau2b718102021-04-21 07:32:39 +0200285 LIST_APPEND(dst, &new->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200286 }
287 return 1;
288
289 error:
290 free(new);
291 return 0;
292}
293
294/* Looks for a shared tcp-check ruleset given its name. */
295struct tcpcheck_ruleset *find_tcpcheck_ruleset(const char *name)
296{
297 struct tcpcheck_ruleset *rs;
298 struct ebpt_node *node;
299
300 node = ebis_lookup_len(&shared_tcpchecks, name, strlen(name));
301 if (node) {
302 rs = container_of(node, typeof(*rs), node);
303 return rs;
304 }
305 return NULL;
306}
307
308/* Creates a new shared tcp-check ruleset and insert it in shared_tcpchecks
309 * tree.
310 */
311struct tcpcheck_ruleset *create_tcpcheck_ruleset(const char *name)
312{
313 struct tcpcheck_ruleset *rs;
314
315 rs = calloc(1, sizeof(*rs));
316 if (rs == NULL)
317 return NULL;
318
319 rs->node.key = strdup(name);
320 if (rs->node.key == NULL) {
321 free(rs);
322 return NULL;
323 }
324
325 LIST_INIT(&rs->rules);
326 ebis_insert(&shared_tcpchecks, &rs->node);
327 return rs;
328}
329
330/* Releases memory allocated by a tcp-check ruleset. */
331void free_tcpcheck_ruleset(struct tcpcheck_ruleset *rs)
332{
333 struct tcpcheck_rule *r, *rb;
334
335 if (!rs)
336 return;
337
338 ebpt_delete(&rs->node);
339 free(rs->node.key);
340 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +0200341 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200342 free_tcpcheck(r, 0);
343 }
344 free(rs);
345}
346
347
348/**************************************************************************/
349/**************** Everything about tcp-checks execution *******************/
350/**************************************************************************/
351/* Returns the id of a step in a tcp-check ruleset */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200352int tcpcheck_get_step_id(const struct check *check, const struct tcpcheck_rule *rule)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200353{
354 if (!rule)
355 rule = check->current_step;
356
357 /* no last started step => first step */
358 if (!rule)
359 return 1;
360
361 /* last step is the first implicit connect */
362 if (rule->index == 0 &&
363 rule->action == TCPCHK_ACT_CONNECT &&
364 (rule->connect.options & TCPCHK_OPT_IMPLICIT))
365 return 0;
366
367 return rule->index + 1;
368}
369
370/* Returns the first non COMMENT/ACTION_KW tcp-check rule from list <list> or
371 * NULL if none was found.
372 */
Christopher Faulet147b8c92021-04-10 09:00:38 +0200373struct tcpcheck_rule *get_first_tcpcheck_rule(const struct tcpcheck_rules *rules)
Willy Tarreau51cd5952020-06-05 12:25:38 +0200374{
375 struct tcpcheck_rule *r;
376
377 list_for_each_entry(r, rules->list, list) {
378 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
379 return r;
380 }
381 return NULL;
382}
383
384/* Returns the last non COMMENT/ACTION_KW tcp-check rule from list <list> or
385 * NULL if none was found.
386 */
387static struct tcpcheck_rule *get_last_tcpcheck_rule(struct tcpcheck_rules *rules)
388{
389 struct tcpcheck_rule *r;
390
391 list_for_each_entry_rev(r, rules->list, list) {
392 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
393 return r;
394 }
395 return NULL;
396}
397
398/* Returns the non COMMENT/ACTION_KW tcp-check rule from list <list> following
399 * <start> or NULL if non was found. If <start> is NULL, it relies on
400 * get_first_tcpcheck_rule().
401 */
402static struct tcpcheck_rule *get_next_tcpcheck_rule(struct tcpcheck_rules *rules, struct tcpcheck_rule *start)
403{
404 struct tcpcheck_rule *r;
405
406 if (!start)
407 return get_first_tcpcheck_rule(rules);
408
409 r = LIST_NEXT(&start->list, typeof(r), list);
410 list_for_each_entry_from(r, rules->list, list) {
411 if (r->action != TCPCHK_ACT_COMMENT && r->action != TCPCHK_ACT_ACTION_KW)
412 return r;
413 }
414 return NULL;
415}
416
417
418/* Creates info message when a tcp-check healthcheck fails on an expect rule */
419static void tcpcheck_expect_onerror_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
420 int match, struct ist info)
421{
422 struct sample *smp;
423
424 /* Follows these step to produce the info message:
425 * 1. if info field is already provided, copy it
426 * 2. if the expect rule provides an onerror log-format string,
427 * use it to produce the message
428 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
429 * 4. Otherwise produce the generic tcp-check info message
430 */
431 if (istlen(info)) {
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100432 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200433 goto comment;
434 }
435 else if (!LIST_ISEMPTY(&rule->expect.onerror_fmt)) {
436 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg), &rule->expect.onerror_fmt);
437 goto comment;
438 }
439
440 if (check->type == PR_O2_TCPCHK_CHK &&
441 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) != TCPCHK_RULES_TCP_CHK)
442 goto comment;
443
444 chunk_strcat(msg, (match ? "TCPCHK matched unwanted content" : "TCPCHK did not match content"));
445 switch (rule->expect.type) {
446 case TCPCHK_EXPECT_HTTP_STATUS:
447 chunk_appendf(msg, "(status codes) at step %d", tcpcheck_get_step_id(check, rule));
448 break;
449 case TCPCHK_EXPECT_STRING:
450 case TCPCHK_EXPECT_HTTP_BODY:
451 chunk_appendf(msg, " '%.*s' at step %d", (unsigned int)istlen(rule->expect.data), istptr(rule->expect.data),
452 tcpcheck_get_step_id(check, rule));
453 break;
454 case TCPCHK_EXPECT_BINARY:
455 chunk_appendf(msg, " (binary) at step %d", tcpcheck_get_step_id(check, rule));
456 break;
457 case TCPCHK_EXPECT_STRING_REGEX:
458 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
459 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
460 chunk_appendf(msg, " (regex) at step %d", tcpcheck_get_step_id(check, rule));
461 break;
462 case TCPCHK_EXPECT_BINARY_REGEX:
463 chunk_appendf(msg, " (binary regex) at step %d", tcpcheck_get_step_id(check, rule));
464 break;
465 case TCPCHK_EXPECT_STRING_LF:
466 case TCPCHK_EXPECT_HTTP_BODY_LF:
467 chunk_appendf(msg, " (log-format string) at step %d", tcpcheck_get_step_id(check, rule));
468 break;
469 case TCPCHK_EXPECT_BINARY_LF:
470 chunk_appendf(msg, " (log-format binary) at step %d", tcpcheck_get_step_id(check, rule));
471 break;
472 case TCPCHK_EXPECT_CUSTOM:
473 chunk_appendf(msg, " (custom function) at step %d", tcpcheck_get_step_id(check, rule));
474 break;
475 case TCPCHK_EXPECT_HTTP_HEADER:
476 chunk_appendf(msg, " (header pattern) at step %d", tcpcheck_get_step_id(check, rule));
477 case TCPCHK_EXPECT_UNDEF:
478 /* Should never happen. */
479 return;
480 }
481
482 comment:
483 /* If the failing expect rule provides a comment, it is concatenated to
484 * the info message.
485 */
486 if (rule->comment) {
487 chunk_strcat(msg, " comment: ");
488 chunk_strcat(msg, rule->comment);
489 }
490
491 /* Finally, the check status code is set if the failing expect rule
492 * defines a status expression.
493 */
494 if (rule->expect.status_expr) {
495 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
496 rule->expect.status_expr, SMP_T_STR);
497
498 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
499 sample_casts[smp->data.type][SMP_T_SINT](smp))
500 check->code = smp->data.u.sint;
501 }
502
503 *(b_tail(msg)) = '\0';
504}
505
506/* Creates info message when a tcp-check healthcheck succeeds on an expect rule */
507static void tcpcheck_expect_onsuccess_message(struct buffer *msg, struct check *check, struct tcpcheck_rule *rule,
508 struct ist info)
509{
510 struct sample *smp;
511
512 /* Follows these step to produce the info message:
513 * 1. if info field is already provided, copy it
514 * 2. if the expect rule provides an onsucces log-format string,
515 * use it to produce the message
516 * 3. the expect rule is part of a protocol check (http, redis, mysql...), do nothing
517 * 4. Otherwise produce the generic tcp-check info message
518 */
519 if (istlen(info))
Tim Duesterhus9f7ed8a2021-11-08 09:05:04 +0100520 chunk_istcat(msg, info);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200521 if (!LIST_ISEMPTY(&rule->expect.onsuccess_fmt))
522 msg->data += sess_build_logline(check->sess, NULL, b_tail(msg), b_room(msg),
523 &rule->expect.onsuccess_fmt);
524 else if (check->type == PR_O2_TCPCHK_CHK &&
525 (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK)
526 chunk_strcat(msg, "(tcp-check)");
527
528 /* Finally, the check status code is set if the expect rule defines a
529 * status expression.
530 */
531 if (rule->expect.status_expr) {
532 smp = sample_fetch_as_type(check->proxy, check->sess, NULL, SMP_OPT_DIR_RES | SMP_OPT_FINAL,
533 rule->expect.status_expr, SMP_T_STR);
534
535 if (smp && sample_casts[smp->data.type][SMP_T_SINT] &&
536 sample_casts[smp->data.type][SMP_T_SINT](smp))
537 check->code = smp->data.u.sint;
538 }
539
540 *(b_tail(msg)) = '\0';
541}
542
543/* Internal functions to parse and validate a MySQL packet in the context of an
544 * expect rule. It start to parse the input buffer at the offset <offset>. If
545 * <last_read> is set, no more data are expected.
546 */
547static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
548 unsigned int offset, int last_read)
549{
550 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
551 enum healthcheck_status status;
552 struct buffer *msg = NULL;
553 struct ist desc = IST_NULL;
554 unsigned int err = 0, plen = 0;
555
556
Christopher Faulet147b8c92021-04-10 09:00:38 +0200557 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
558
Willy Tarreau51cd5952020-06-05 12:25:38 +0200559 /* 3 Bytes for the packet length and 1 byte for the sequence id */
560 if (b_data(&check->bi) < offset+4) {
561 if (!last_read)
562 goto wait_more_data;
563
564 /* invalid length or truncated response */
565 status = HCHK_STATUS_L7RSP;
566 goto error;
567 }
568
569 plen = ((unsigned char) *b_peek(&check->bi, offset)) +
570 (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
571 (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
572
573 if (b_data(&check->bi) < offset+plen+4) {
574 if (!last_read)
575 goto wait_more_data;
576
577 /* invalid length or truncated response */
578 status = HCHK_STATUS_L7RSP;
579 goto error;
580 }
581
582 if (*b_peek(&check->bi, offset+4) == '\xff') {
583 /* MySQL Error packet always begin with field_count = 0xff */
584 status = HCHK_STATUS_L7STS;
585 err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
586 (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
587 desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
588 goto error;
589 }
590
591 if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
592 /* Not the last rule, continue */
593 goto out;
594 }
595
596 /* We set the MySQL Version in description for information purpose
597 * FIXME : it can be cool to use MySQL Version for other purpose,
598 * like mark as down old MySQL server.
599 */
600 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
601 set_server_check_status(check, status, b_peek(&check->bi, 5));
602
603 out:
604 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200605 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200606 return ret;
607
608 error:
609 ret = TCPCHK_EVAL_STOP;
610 check->code = err;
611 msg = alloc_trash_chunk();
612 if (msg)
613 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
614 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
615 goto out;
616
617 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200618 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200619 ret = TCPCHK_EVAL_WAIT;
620 goto out;
621}
622
623/* Custom tcp-check expect function to parse and validate the MySQL initial
624 * handshake packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
625 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
626 * error occurred.
627 */
628enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
629{
630 return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
631}
632
633/* Custom tcp-check expect function to parse and validate the MySQL OK packet
634 * following the initial handshake. Returns TCPCHK_EVAL_WAIT to wait for more
635 * data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if
636 * an error occurred.
637 */
638enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
639{
640 unsigned int hslen = 0;
641
642 hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
643 (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
644 (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
645
646 return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
647}
648
649/* Custom tcp-check expect function to parse and validate the LDAP bind response
650 * package packet. Returns TCPCHK_EVAL_WAIT to wait for more data,
651 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
652 * error occurred.
653 */
654enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct tcpcheck_rule *rule, int last_read)
655{
656 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
657 enum healthcheck_status status;
658 struct buffer *msg = NULL;
659 struct ist desc = IST_NULL;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200660 char *ptr;
661 unsigned short nbytes = 0;
662 size_t msglen = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200663
Christopher Faulet147b8c92021-04-10 09:00:38 +0200664 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
665
Willy Tarreau51cd5952020-06-05 12:25:38 +0200666 /* Check if the server speaks LDAP (ASN.1/BER)
667 * http://en.wikipedia.org/wiki/Basic_Encoding_Rules
668 * http://tools.ietf.org/html/rfc4511
669 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200670 ptr = b_head(&check->bi) + 1;
671
Willy Tarreau51cd5952020-06-05 12:25:38 +0200672 /* size of LDAPMessage */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200673 if (*ptr & 0x80) {
674 /* For message size encoded on several bytes, we only handle
675 * size encoded on 2 or 4 bytes. There is no reason to make this
676 * part to complex because only Active Directory is known to
677 * encode BindReponse length on 4 bytes.
678 */
679 nbytes = (*ptr & 0x7f);
680 if (b_data(&check->bi) < 1 + nbytes)
681 goto too_short;
682 switch (nbytes) {
683 case 4: msglen = read_n32(ptr+1); break;
684 case 2: msglen = read_n16(ptr+1); break;
685 default:
686 status = HCHK_STATUS_L7RSP;
687 desc = ist("Not LDAPv3 protocol");
688 goto error;
689 }
690 }
691 else
692 msglen = *ptr;
693 ptr += 1 + nbytes;
694
695 if (b_data(&check->bi) < 2 + nbytes + msglen)
696 goto too_short;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200697
698 /* http://tools.ietf.org/html/rfc4511#section-4.2.2
699 * messageID: 0x02 0x01 0x01: INTEGER 1
700 * protocolOp: 0x61: bindResponse
701 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200702 if (memcmp(ptr, "\x02\x01\x01\x61", 4) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200703 status = HCHK_STATUS_L7RSP;
704 desc = ist("Not LDAPv3 protocol");
705 goto error;
706 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200707 ptr += 4;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200708
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200709 /* skip size of bindResponse */
710 nbytes = 0;
711 if (*ptr & 0x80)
712 nbytes = (*ptr & 0x7f);
713 ptr += 1 + nbytes;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200714
715 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
716 * ldapResult: 0x0a 0x01: ENUMERATION
717 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200718 if (memcmp(ptr, "\x0a\x01", 2) != 0) {
Willy Tarreau51cd5952020-06-05 12:25:38 +0200719 status = HCHK_STATUS_L7RSP;
720 desc = ist("Not LDAPv3 protocol");
721 goto error;
722 }
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200723 ptr += 2;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200724
725 /* http://tools.ietf.org/html/rfc4511#section-4.1.9
726 * resultCode
727 */
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200728 check->code = *ptr;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200729 if (check->code) {
730 status = HCHK_STATUS_L7STS;
731 desc = ist("See RFC: http://tools.ietf.org/html/rfc4511#section-4.1.9");
732 goto error;
733 }
734
735 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
736 set_server_check_status(check, status, "Success");
737
738 out:
739 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200740 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200741 return ret;
742
743 error:
744 ret = TCPCHK_EVAL_STOP;
745 msg = alloc_trash_chunk();
746 if (msg)
747 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
748 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
749 goto out;
Christopher Faulet8a0e5f82021-09-16 16:01:09 +0200750
751 too_short:
752 if (!last_read)
753 goto wait_more_data;
754 /* invalid length or truncated response */
755 status = HCHK_STATUS_L7RSP;
756 goto error;
757
758 wait_more_data:
759 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
760 ret = TCPCHK_EVAL_WAIT;
761 goto out;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200762}
763
764/* Custom tcp-check expect function to parse and validate the SPOP hello agent
765 * frame. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
766 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
767 */
768enum tcpcheck_eval_ret tcpcheck_spop_expect_agenthello(struct check *check, struct tcpcheck_rule *rule, int last_read)
769{
770 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
771 enum healthcheck_status status;
772 struct buffer *msg = NULL;
773 struct ist desc = IST_NULL;
774 unsigned int framesz;
775
Christopher Faulet147b8c92021-04-10 09:00:38 +0200776 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200777
778 memcpy(&framesz, b_head(&check->bi), 4);
779 framesz = ntohl(framesz);
780
781 if (!last_read && b_data(&check->bi) < (4+framesz))
782 goto wait_more_data;
783
784 memset(b_orig(&trash), 0, b_size(&trash));
785 if (spoe_handle_healthcheck_response(b_peek(&check->bi, 4), framesz, b_orig(&trash), HCHK_DESC_LEN) == -1) {
786 status = HCHK_STATUS_L7RSP;
787 desc = ist2(b_orig(&trash), strlen(b_orig(&trash)));
788 goto error;
789 }
790
791 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
792 set_server_check_status(check, status, "SPOA server is ok");
793
794 out:
795 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200796 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +0200797 return ret;
798
799 error:
800 ret = TCPCHK_EVAL_STOP;
801 msg = alloc_trash_chunk();
802 if (msg)
803 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
804 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
805 goto out;
806
807 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +0200808 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200809 ret = TCPCHK_EVAL_WAIT;
810 goto out;
811}
812
813/* Custom tcp-check expect function to parse and validate the agent-check
814 * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE
815 * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred.
816 */
817enum tcpcheck_eval_ret tcpcheck_agent_expect_reply(struct check *check, struct tcpcheck_rule *rule, int last_read)
818{
819 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_STOP;
820 enum healthcheck_status status = HCHK_STATUS_CHECKED;
821 const char *hs = NULL; /* health status */
822 const char *as = NULL; /* admin status */
823 const char *ps = NULL; /* performance status */
824 const char *cs = NULL; /* maxconn */
825 const char *err = NULL; /* first error to report */
826 const char *wrn = NULL; /* first warning to report */
827 char *cmd, *p;
828
Christopher Faulet147b8c92021-04-10 09:00:38 +0200829 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
830
Willy Tarreau51cd5952020-06-05 12:25:38 +0200831 /* We're getting an agent check response. The agent could
832 * have been disabled in the mean time with a long check
833 * still pending. It is important that we ignore the whole
834 * response.
835 */
836 if (!(check->state & CHK_ST_ENABLED))
837 goto out;
838
839 /* The agent supports strings made of a single line ended by the
840 * first CR ('\r') or LF ('\n'). This line is composed of words
841 * delimited by spaces (' '), tabs ('\t'), or commas (','). The
842 * line may optionally contained a description of a state change
843 * after a sharp ('#'), which is only considered if a health state
844 * is announced.
845 *
846 * Words may be composed of :
847 * - a numeric weight suffixed by the percent character ('%').
848 * - a health status among "up", "down", "stopped", and "fail".
849 * - an admin status among "ready", "drain", "maint".
850 *
851 * These words may appear in any order. If multiple words of the
852 * same category appear, the last one wins.
853 */
854
855 p = b_head(&check->bi);
856 while (*p && *p != '\n' && *p != '\r')
857 p++;
858
859 if (!*p) {
860 if (!last_read)
861 goto wait_more_data;
862
863 /* at least inform the admin that the agent is mis-behaving */
864 set_server_check_status(check, check->status, "Ignoring incomplete line from agent");
865 goto out;
866 }
867
868 *p = 0;
869 cmd = b_head(&check->bi);
870
871 while (*cmd) {
872 /* look for next word */
873 if (*cmd == ' ' || *cmd == '\t' || *cmd == ',') {
874 cmd++;
875 continue;
876 }
877
878 if (*cmd == '#') {
879 /* this is the beginning of a health status description,
880 * skip the sharp and blanks.
881 */
882 cmd++;
883 while (*cmd == '\t' || *cmd == ' ')
884 cmd++;
885 break;
886 }
887
888 /* find the end of the word so that we have a null-terminated
889 * word between <cmd> and <p>.
890 */
891 p = cmd + 1;
892 while (*p && *p != '\t' && *p != ' ' && *p != '\n' && *p != ',')
893 p++;
894 if (*p)
895 *p++ = 0;
896
897 /* first, health statuses */
898 if (strcasecmp(cmd, "up") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100899 check->health = check->rise + check->fall - 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200900 status = HCHK_STATUS_L7OKD;
901 hs = cmd;
902 }
903 else if (strcasecmp(cmd, "down") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100904 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200905 status = HCHK_STATUS_L7STS;
906 hs = cmd;
907 }
908 else if (strcasecmp(cmd, "stopped") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100909 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200910 status = HCHK_STATUS_L7STS;
911 hs = cmd;
912 }
913 else if (strcasecmp(cmd, "fail") == 0) {
Christopher Faulet24ec9432021-03-12 09:06:07 +0100914 check->health = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +0200915 status = HCHK_STATUS_L7STS;
916 hs = cmd;
917 }
918 /* admin statuses */
919 else if (strcasecmp(cmd, "ready") == 0) {
920 as = cmd;
921 }
922 else if (strcasecmp(cmd, "drain") == 0) {
923 as = cmd;
924 }
925 else if (strcasecmp(cmd, "maint") == 0) {
926 as = cmd;
927 }
928 /* try to parse a weight here and keep the last one */
929 else if (isdigit((unsigned char)*cmd) && strchr(cmd, '%') != NULL) {
930 ps = cmd;
931 }
932 /* try to parse a maxconn here */
933 else if (strncasecmp(cmd, "maxconn:", strlen("maxconn:")) == 0) {
934 cs = cmd;
935 }
936 else {
937 /* keep a copy of the first error */
938 if (!err)
939 err = cmd;
940 }
941 /* skip to next word */
942 cmd = p;
943 }
944 /* here, cmd points either to \0 or to the beginning of a
945 * description. Skip possible leading spaces.
946 */
947 while (*cmd == ' ' || *cmd == '\n')
948 cmd++;
949
950 /* First, update the admin status so that we avoid sending other
951 * possibly useless warnings and can also update the health if
952 * present after going back up.
953 */
954 if (as) {
Christopher Faulet147b8c92021-04-10 09:00:38 +0200955 if (strcasecmp(as, "drain") == 0) {
956 TRACE_DEVEL("set server into DRAIN mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200957 srv_adm_set_drain(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200958 }
959 else if (strcasecmp(as, "maint") == 0) {
960 TRACE_DEVEL("set server into MAINT mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200961 srv_adm_set_maint(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200962 }
963 else {
964 TRACE_DEVEL("set server into READY mode", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200965 srv_adm_set_ready(check->server);
Christopher Faulet147b8c92021-04-10 09:00:38 +0200966 }
Willy Tarreau51cd5952020-06-05 12:25:38 +0200967 }
968
969 /* now change weights */
970 if (ps) {
971 const char *msg;
972
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +0500973 TRACE_DEVEL("change server weight", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +0200974 msg = server_parse_weight_change_request(check->server, ps);
975 if (!wrn || !*wrn)
976 wrn = msg;
977 }
978
979 if (cs) {
980 const char *msg;
981
982 cs += strlen("maxconn:");
983
Christopher Faulet147b8c92021-04-10 09:00:38 +0200984 TRACE_DEVEL("change server maxconn", CHK_EV_TCPCHK_EXP, check);
Amaury Denoyelle02742862021-06-18 11:11:36 +0200985 /* This is safe to call server_parse_maxconn_change_request
986 * because the server lock is held during the check.
987 */
Willy Tarreau51cd5952020-06-05 12:25:38 +0200988 msg = server_parse_maxconn_change_request(check->server, cs);
989 if (!wrn || !*wrn)
990 wrn = msg;
991 }
992
993 /* and finally health status */
994 if (hs) {
995 /* We'll report some of the warnings and errors we have
996 * here. Down reports are critical, we leave them untouched.
997 * Lack of report, or report of 'UP' leaves the room for
998 * ERR first, then WARN.
999 */
1000 const char *msg = cmd;
1001 struct buffer *t;
1002
1003 if (!*msg || status == HCHK_STATUS_L7OKD) {
1004 if (err && *err)
1005 msg = err;
1006 else if (wrn && *wrn)
1007 msg = wrn;
1008 }
1009
1010 t = get_trash_chunk();
1011 chunk_printf(t, "via agent : %s%s%s%s",
1012 hs, *msg ? " (" : "",
1013 msg, *msg ? ")" : "");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001014 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001015 set_server_check_status(check, status, t->area);
1016 }
1017 else if (err && *err) {
1018 /* No status change but we'd like to report something odd.
1019 * Just report the current state and copy the message.
1020 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001021 TRACE_DEVEL("agent reports an error", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001022 chunk_printf(&trash, "agent reports an error : %s", err);
1023 set_server_check_status(check, status/*check->status*/, trash.area);
1024 }
1025 else if (wrn && *wrn) {
1026 /* No status change but we'd like to report something odd.
1027 * Just report the current state and copy the message.
1028 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001029 TRACE_DEVEL("agent reports a warning", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001030 chunk_printf(&trash, "agent warns : %s", wrn);
1031 set_server_check_status(check, status/*check->status*/, trash.area);
1032 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001033 else {
1034 TRACE_DEVEL("update server health status", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001035 set_server_check_status(check, status, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001036 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001037
1038 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001039 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001040 return ret;
1041
1042 wait_more_data:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001043 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001044 ret = TCPCHK_EVAL_WAIT;
1045 goto out;
1046}
1047
1048/* Evaluates a TCPCHK_ACT_CONNECT rule. Returns TCPCHK_EVAL_WAIT to wait the
1049 * connection establishment, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1050 * TCPCHK_EVAL_STOP if an error occurred.
1051 */
1052enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
1053{
1054 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1055 struct tcpcheck_connect *connect = &rule->connect;
1056 struct proxy *proxy = check->proxy;
1057 struct server *s = check->server;
1058 struct task *t = check->task;
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001059 struct connection *conn = cs_conn(check->cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001060 struct protocol *proto;
1061 struct xprt_ops *xprt;
1062 struct tcpcheck_rule *next;
1063 int status, port;
1064
Christopher Faulet147b8c92021-04-10 09:00:38 +02001065 TRACE_ENTER(CHK_EV_TCPCHK_CONN, check);
1066
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001067 next = get_next_tcpcheck_rule(check->tcpcheck_rules, rule);
1068
1069 /* current connection already created, check if it is established or not */
1070 if (conn) {
1071 if (conn->flags & CO_FL_WAIT_XPRT) {
1072 /* We are still waiting for the connection establishment */
1073 if (next && next->action == TCPCHK_ACT_SEND) {
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001074 if (!(check->cs->wait_event.events & SUB_RETRY_SEND))
1075 conn->mux->subscribe(check->cs, SUB_RETRY_SEND, &check->cs->wait_event);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001076 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001077 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001078 }
1079 else
1080 ret = tcpcheck_eval_recv(check, rule);
1081 }
1082 goto out;
1083 }
1084
1085 /* Note: here check->cs = cs = conn = NULL */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001086
Christopher Fauletb381a502020-11-25 13:47:00 +01001087 /* Always release input and output buffer when a new connect is evaluated */
1088 check_release_buf(check, &check->bi);
1089 check_release_buf(check, &check->bo);
1090
Christopher Fauletb1bb0692020-11-25 16:47:30 +01001091 /* No connection, prepare a new one */
Christopher Fauletdd2d0d82021-12-20 09:34:32 +01001092 conn = conn_new((s ? &s->obj_type : &proxy->obj_type));
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001093 if (!conn) {
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001094 chunk_printf(&trash, "TCPCHK error allocating connection at step %d",
1095 tcpcheck_get_step_id(check, rule));
1096 if (rule->comment)
1097 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1098 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
1099 ret = TCPCHK_EVAL_STOP;
Willy Tarreau4596fe22022-05-17 19:07:51 +02001100 TRACE_ERROR("stconn allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Fauletcda94ac2021-12-23 17:28:17 +01001101 goto out;
1102 }
Christopher Faulet070b91b2022-03-31 19:27:18 +02001103 if (cs_attach_mux(check->cs, NULL, conn) < 0) {
1104 TRACE_ERROR("mux attach error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
1105 conn_free(conn);
1106 conn = NULL;
1107 status = SF_ERR_RESOURCE;
1108 goto fail_check;
1109 }
Christopher Fauleta9e8b392022-03-23 11:01:09 +01001110 conn->ctx = check->cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001111 conn_set_owner(conn, check->sess, NULL);
1112
Willy Tarreau51cd5952020-06-05 12:25:38 +02001113 /* no client address */
Willy Tarreau9b7587a2020-10-15 07:32:10 +02001114 if (!sockaddr_alloc(&conn->dst, NULL, 0)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001115 TRACE_ERROR("sockaddr allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001116 status = SF_ERR_RESOURCE;
1117 goto fail_check;
1118 }
1119
1120 /* connect to the connect rule addr if specified, otherwise the check
1121 * addr if specified on the server. otherwise, use the server addr (it
1122 * MUST exist at this step).
1123 */
1124 *conn->dst = (is_addr(&connect->addr)
1125 ? connect->addr
1126 : (is_addr(&check->addr) ? check->addr : s->addr));
Willy Tarreau14e7f292021-10-27 17:41:07 +02001127 proto = protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, 0);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001128
1129 port = 0;
Christopher Fauletb1d19ea2021-02-12 16:09:13 +01001130 if (connect->port)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001131 port = connect->port;
1132 if (!port && connect->port_expr) {
1133 struct sample *smp;
1134
1135 smp = sample_fetch_as_type(check->proxy, check->sess, NULL,
1136 SMP_OPT_DIR_REQ | SMP_OPT_FINAL,
1137 connect->port_expr, SMP_T_SINT);
1138 if (smp)
1139 port = smp->data.u.sint;
1140 }
1141 if (!port && is_inet_addr(&connect->addr))
1142 port = get_host_port(&connect->addr);
1143 if (!port && check->port)
1144 port = check->port;
1145 if (!port && is_inet_addr(&check->addr))
1146 port = get_host_port(&check->addr);
1147 if (!port) {
1148 /* The server MUST exist here */
1149 port = s->svc_port;
1150 }
1151 set_host_port(conn->dst, port);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001152 TRACE_DEVEL("set port", CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){port});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001153
1154 xprt = ((connect->options & TCPCHK_OPT_SSL)
1155 ? xprt_get(XPRT_SSL)
1156 : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
1157
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001158 if (conn_prepare(conn, proto, xprt) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001159 TRACE_ERROR("xprt allocation error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001160 status = SF_ERR_RESOURCE;
1161 goto fail_check;
1162 }
1163
Christopher Fauletf7177272020-10-02 13:41:55 +02001164 if ((connect->options & TCPCHK_OPT_SOCKS4) && s && (s->flags & SRV_F_SOCKS4_PROXY)) {
1165 conn->send_proxy_ofs = 1;
1166 conn->flags |= CO_FL_SOCKS4;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001167 TRACE_DEVEL("configure SOCKS4 proxy", CHK_EV_TCPCHK_CONN);
Christopher Fauletf7177272020-10-02 13:41:55 +02001168 }
1169 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.via_socks4 && (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
1175 if (connect->options & TCPCHK_OPT_SEND_PROXY) {
1176 conn->send_proxy_ofs = 1;
1177 conn->flags |= CO_FL_SEND_PROXY;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001178 TRACE_DEVEL("configure PROXY protocol", CHK_EV_TCPCHK_CONN, check);
Christopher Fauletf7177272020-10-02 13:41:55 +02001179 }
1180 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.send_proxy && !(check->state & CHK_ST_AGENT)) {
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
Willy Tarreau51cd5952020-06-05 12:25:38 +02001186 status = SF_ERR_INTERNAL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001187 if (proto && proto->connect) {
1188 int flags = 0;
1189
1190 if (check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK)
1191 flags |= CONNECT_HAS_DATA;
1192 if (!next || next->action != TCPCHK_ACT_EXPECT)
1193 flags |= CONNECT_DELACK_ALWAYS;
1194 status = proto->connect(conn, flags);
1195 }
1196
1197 if (status != SF_ERR_NONE)
1198 goto fail_check;
1199
Christopher Faulet21ddc742020-07-01 15:26:14 +02001200 conn_set_private(conn);
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001201 conn->ctx = check->cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001202
Willy Tarreau51cd5952020-06-05 12:25:38 +02001203#ifdef USE_OPENSSL
1204 if (connect->sni)
1205 ssl_sock_set_servername(conn, connect->sni);
1206 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.sni)
1207 ssl_sock_set_servername(conn, s->check.sni);
1208
1209 if (connect->alpn)
1210 ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
1211 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s && s->check.alpn_str)
1212 ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
1213#endif
Willy Tarreau51cd5952020-06-05 12:25:38 +02001214
Willy Tarreaue2226792022-04-11 18:04:33 +02001215 if (conn_ctrl_ready(conn) && (connect->options & TCPCHK_OPT_LINGER) && !(conn->flags & CO_FL_FDLESS)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001216 /* Some servers don't like reset on close */
Christopher Faulet897d6122021-12-17 17:28:35 +01001217 HA_ATOMIC_AND(&fdtab[conn->handle.fd].state, ~FD_LINGER_RISK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001218 }
1219
1220 if (conn_ctrl_ready(conn) && (conn->flags & (CO_FL_SEND_PROXY | CO_FL_SOCKS4))) {
1221 if (xprt_add_hs(conn) < 0)
1222 status = SF_ERR_RESOURCE;
1223 }
1224
Olivier Houchard1b3c9312021-03-05 23:37:48 +01001225 if (conn_xprt_start(conn) < 0) {
1226 status = SF_ERR_RESOURCE;
1227 goto fail_check;
1228 }
1229
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001230 /* The mux may be initialized now if there isn't server attached to the
1231 * check (email alerts) or if there is a mux proto specified or if there
1232 * is no alpn.
1233 */
1234 if (!s || ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto) ||
1235 connect->mux_proto || (!connect->alpn && !check->alpn_str)) {
1236 const struct mux_ops *mux_ops;
1237
Christopher Faulet147b8c92021-04-10 09:00:38 +02001238 TRACE_DEVEL("try to install mux now", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001239 if (connect->mux_proto)
1240 mux_ops = connect->mux_proto->mux;
1241 else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && check->mux_proto)
1242 mux_ops = check->mux_proto->mux;
1243 else {
1244 int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
1245 ? PROTO_MODE_HTTP
1246 : PROTO_MODE_TCP);
1247
1248 mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
1249 }
Christopher Faulet54e85cb2022-01-06 08:46:56 +01001250 if (mux_ops && conn_install_mux(conn, mux_ops, check->cs, proxy, check->sess) < 0) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001251 TRACE_ERROR("failed to install mux", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau8e979fa2020-07-31 08:49:31 +02001252 status = SF_ERR_INTERNAL;
1253 goto fail_check;
1254 }
1255 }
1256
Willy Tarreau51cd5952020-06-05 12:25:38 +02001257 fail_check:
1258 /* It can return one of :
1259 * - SF_ERR_NONE if everything's OK
1260 * - SF_ERR_SRVTO if there are no more servers
1261 * - SF_ERR_SRVCL if the connection was refused by the server
1262 * - SF_ERR_PRXCOND if the connection has been limited by the proxy (maxconn)
1263 * - SF_ERR_RESOURCE if a system resource is lacking (eg: fd limits, ports, ...)
1264 * - SF_ERR_INTERNAL for any other purely internal errors
1265 * Additionally, in the case of SF_ERR_RESOURCE, an emergency log will be emitted.
1266 * Note that we try to prevent the network stack from sending the ACK during the
1267 * connect() when a pure TCP check is used (without PROXY protocol).
1268 */
1269 switch (status) {
1270 case SF_ERR_NONE:
1271 /* we allow up to min(inter, timeout.connect) for a connection
1272 * to establish but only when timeout.check is set as it may be
1273 * to short for a full check otherwise
1274 */
1275 t->expire = tick_add(now_ms, MS_TO_TICKS(check->inter));
1276
1277 if (proxy->timeout.check && proxy->timeout.connect) {
1278 int t_con = tick_add(now_ms, proxy->timeout.connect);
1279 t->expire = tick_first(t->expire, t_con);
1280 }
1281 break;
1282 case SF_ERR_SRVTO: /* ETIMEDOUT */
1283 case SF_ERR_SRVCL: /* ECONNREFUSED, ENETUNREACH, ... */
1284 case SF_ERR_PRXCOND:
1285 case SF_ERR_RESOURCE:
1286 case SF_ERR_INTERNAL:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001287 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 +02001288 chk_report_conn_err(check, errno, 0);
1289 ret = TCPCHK_EVAL_STOP;
1290 goto out;
1291 }
1292
1293 /* don't do anything until the connection is established */
1294 if (conn->flags & CO_FL_WAIT_XPRT) {
1295 if (conn->mux) {
1296 if (next && next->action == TCPCHK_ACT_SEND)
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001297 conn->mux->subscribe(check->cs, SUB_RETRY_SEND, &check->cs->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001298 else
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001299 conn->mux->subscribe(check->cs, SUB_RETRY_RECV, &check->cs->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001300 }
1301 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001302 TRACE_DEVEL("not connected yet", CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001303 goto out;
1304 }
1305
1306 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001307 if (conn && check->result == CHK_RES_FAILED) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001308 conn->flags |= CO_FL_ERROR;
Ilya Shipitsinb2be9a12021-04-24 13:25:42 +05001309 TRACE_ERROR("connect failed, report connection error", CHK_EV_TCPCHK_CONN|CHK_EV_TCPCHK_ERR, check);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001310 }
Christopher Fauletc878f562020-12-09 19:46:38 +01001311
1312 if (ret == TCPCHK_EVAL_CONTINUE && check->proxy->timeout.check)
1313 check->task->expire = tick_add_ifset(now_ms, check->proxy->timeout.check);
1314
Christopher Faulet147b8c92021-04-10 09:00:38 +02001315 TRACE_LEAVE(CHK_EV_TCPCHK_CONN, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001316 return ret;
1317}
1318
1319/* Evaluates a TCPCHK_ACT_SEND rule. Returns TCPCHK_EVAL_WAIT if outgoing data
1320 * were not fully sent, TCPCHK_EVAL_CONTINUE to evaluate the next rule or
1321 * TCPCHK_EVAL_STOP if an error occurred.
1322 */
1323enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcpcheck_rule *rule)
1324{
1325 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1326 struct tcpcheck_send *send = &rule->send;
Willy Tarreau4596fe22022-05-17 19:07:51 +02001327 struct stconn *cs = check->cs;
Christopher Faulet693b23b2022-02-28 09:09:05 +01001328 struct connection *conn = __cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001329 struct buffer *tmp = NULL;
1330 struct htx *htx = NULL;
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001331 int connection_hdr = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001332
Christopher Faulet147b8c92021-04-10 09:00:38 +02001333 TRACE_ENTER(CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
1334
Christopher Fauletb381a502020-11-25 13:47:00 +01001335 if (check->state & CHK_ST_OUT_ALLOC) {
1336 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001337 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 +01001338 goto out;
1339 }
1340
1341 if (!check_get_buf(check, &check->bo)) {
1342 check->state |= CHK_ST_OUT_ALLOC;
1343 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001344 TRACE_STATE("waiting for output buffer allocation", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA|CHK_EV_TX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001345 goto out;
1346 }
1347
Christopher Faulet39066c22020-11-25 13:34:51 +01001348 /* Data already pending in the output buffer, send them now */
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001349 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 +02001350 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 +01001351 goto do_send;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001352 }
Christopher Faulet39066c22020-11-25 13:34:51 +01001353
Christopher Fauletb381a502020-11-25 13:47:00 +01001354 /* Always release input buffer when a new send is evaluated */
1355 check_release_buf(check, &check->bi);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001356
1357 switch (send->type) {
1358 case TCPCHK_SEND_STRING:
1359 case TCPCHK_SEND_BINARY:
1360 if (istlen(send->data) >= b_size(&check->bo)) {
1361 chunk_printf(&trash, "tcp-check send : string too large (%u) for buffer size (%u) at step %d",
1362 (unsigned int)istlen(send->data), (unsigned int)b_size(&check->bo),
1363 tcpcheck_get_step_id(check, rule));
1364 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1365 ret = TCPCHK_EVAL_STOP;
1366 goto out;
1367 }
1368 b_putist(&check->bo, send->data);
1369 break;
1370 case TCPCHK_SEND_STRING_LF:
1371 check->bo.data = sess_build_logline(check->sess, NULL, b_orig(&check->bo), b_size(&check->bo), &rule->send.fmt);
1372 if (!b_data(&check->bo))
1373 goto out;
1374 break;
1375 case TCPCHK_SEND_BINARY_LF: {
1376 int len = b_size(&check->bo);
1377
1378 tmp = alloc_trash_chunk();
1379 if (!tmp)
1380 goto error_lf;
1381 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &rule->send.fmt);
1382 if (!b_data(tmp))
1383 goto out;
1384 tmp->area[tmp->data] = '\0';
1385 if (parse_binary(b_orig(tmp), &check->bo.area, &len, NULL) == 0)
1386 goto error_lf;
1387 check->bo.data = len;
1388 break;
1389 }
1390 case TCPCHK_SEND_HTTP: {
1391 struct htx_sl *sl;
1392 struct ist meth, uri, vsn, clen, body;
1393 unsigned int slflags = 0;
1394
1395 tmp = alloc_trash_chunk();
1396 if (!tmp)
1397 goto error_htx;
1398
1399 meth = ((send->http.meth.meth == HTTP_METH_OTHER)
1400 ? ist2(send->http.meth.str.area, send->http.meth.str.data)
1401 : http_known_methods[send->http.meth.meth]);
1402 if (send->http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
1403 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.uri_fmt);
1404 uri = (b_data(tmp) ? ist2(b_orig(tmp), b_data(tmp)) : ist("/"));
1405 }
1406 else
1407 uri = (isttest(send->http.uri) ? send->http.uri : ist("/"));
1408 vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
1409
1410 if ((istlen(vsn) == 6 && *(vsn.ptr+5) == '2') ||
1411 (istlen(vsn) == 8 && (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1'))))
1412 slflags |= HTX_SL_F_VER_11;
1413 slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
1414 if (!isttest(send->http.body))
1415 slflags |= HTX_SL_F_BODYLESS;
1416
1417 htx = htx_from_buf(&check->bo);
1418 sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
1419 if (!sl)
1420 goto error_htx;
1421 sl->info.req.meth = send->http.meth.meth;
1422 if (!http_update_host(htx, sl, uri))
1423 goto error_htx;
1424
1425 if (!LIST_ISEMPTY(&send->http.hdrs)) {
1426 struct tcpcheck_http_hdr *hdr;
1427 struct ist hdr_value;
1428
1429 list_for_each_entry(hdr, &send->http.hdrs, list) {
1430 chunk_reset(tmp);
1431 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
1432 if (!b_data(tmp))
1433 continue;
1434 hdr_value = ist2(b_orig(tmp), b_data(tmp));
1435 if (!htx_add_header(htx, hdr->name, hdr_value))
1436 goto error_htx;
1437 if ((sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteqi(hdr->name, ist("host"))) {
1438 if (!http_update_authority(htx, sl, hdr_value))
1439 goto error_htx;
1440 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001441 if (isteqi(hdr->name, ist("connection")))
1442 connection_hdr = 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001443 }
1444
1445 }
1446 if (check->proxy->options2 & PR_O2_CHK_SNDST) {
1447 chunk_reset(tmp);
1448 httpchk_build_status_header(check->server, tmp);
1449 if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
1450 goto error_htx;
1451 }
1452
1453
1454 if (send->http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
1455 chunk_reset(tmp);
1456 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &send->http.body_fmt);
1457 body = ist2(b_orig(tmp), b_data(tmp));
1458 }
1459 else
1460 body = send->http.body;
1461 clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
1462
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01001463 if ((!connection_hdr && !htx_add_header(htx, ist("Connection"), ist("close"))) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001464 !htx_add_header(htx, ist("Content-length"), clen))
1465 goto error_htx;
1466
1467
1468 if (!htx_add_endof(htx, HTX_BLK_EOH) ||
Christopher Faulet810df062020-07-22 16:20:34 +02001469 (istlen(body) && !htx_add_data_atonce(htx, body)))
1470 goto error_htx;
1471
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001472 /* no more data are expected */
1473 htx->flags |= HTX_FL_EOM;
Willy Tarreau51cd5952020-06-05 12:25:38 +02001474 htx_to_buf(htx, &check->bo);
1475 break;
1476 }
1477 case TCPCHK_SEND_UNDEF:
1478 /* Should never happen. */
1479 ret = TCPCHK_EVAL_STOP;
1480 goto out;
1481 };
1482
Christopher Faulet39066c22020-11-25 13:34:51 +01001483 do_send:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001484 TRACE_DATA("send data", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001485 if (conn->mux->snd_buf(cs, &check->bo,
1486 (IS_HTX_CONN(conn) ? (htxbuf(&check->bo))->data: b_data(&check->bo)), 0) <= 0) {
Willy Tarreaub605c422022-05-17 17:04:55 +02001487 if ((conn->flags & CO_FL_ERROR) || sc_ep_test(cs, SE_FL_ERROR)) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001488 ret = TCPCHK_EVAL_STOP;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001489 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 +02001490 goto out;
1491 }
1492 }
Christopher Faulet47bfd7b2021-08-11 15:46:29 +02001493 if ((IS_HTX_CONN(conn) && !htx_is_empty(htxbuf(&check->bo))) || (!IS_HTX_CONN(conn) && b_data(&check->bo))) {
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001494 conn->mux->subscribe(cs, SUB_RETRY_SEND, &cs->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001495 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001496 TRACE_DEVEL("data not fully sent, wait", CHK_EV_TCPCHK_SND|CHK_EV_TX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001497 goto out;
1498 }
1499
1500 out:
1501 free_trash_chunk(tmp);
Christopher Fauletb381a502020-11-25 13:47:00 +01001502 if (!b_data(&check->bo) || ret == TCPCHK_EVAL_STOP)
1503 check_release_buf(check, &check->bo);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001504
1505 TRACE_LEAVE(CHK_EV_TCPCHK_SND, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001506 return ret;
1507
1508 error_htx:
1509 if (htx) {
1510 htx_reset(htx);
1511 htx_to_buf(htx, &check->bo);
1512 }
1513 chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
1514 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001515 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 +02001516 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1517 ret = TCPCHK_EVAL_STOP;
1518 goto out;
1519
1520 error_lf:
1521 chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
1522 tcpcheck_get_step_id(check, rule));
Christopher Faulet147b8c92021-04-10 09:00:38 +02001523 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 +02001524 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
1525 ret = TCPCHK_EVAL_STOP;
1526 goto out;
1527
1528}
1529
1530/* Try to receive data before evaluating a tcp-check expect rule. Returns
1531 * TCPCHK_EVAL_WAIT if it is already subscribed on receive events or if nothing
1532 * was received, TCPCHK_EVAL_CONTINUE to evaluate the expect rule or
1533 * TCPCHK_EVAL_STOP if an error occurred.
1534 */
1535enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcpcheck_rule *rule)
1536{
Willy Tarreau4596fe22022-05-17 19:07:51 +02001537 struct stconn *cs = check->cs;
Christopher Faulet693b23b2022-02-28 09:09:05 +01001538 struct connection *conn = __cs_conn(cs);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001539 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1540 size_t max, read, cur_read = 0;
1541 int is_empty;
1542 int read_poll = MAX_READ_POLL_LOOPS;
1543
Christopher Faulet147b8c92021-04-10 09:00:38 +02001544 TRACE_ENTER(CHK_EV_RX_DATA, check);
1545
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001546 if (cs->wait_event.events & SUB_RETRY_RECV) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001547 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001548 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001549 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001550
Willy Tarreaub605c422022-05-17 17:04:55 +02001551 if (sc_ep_test(cs, SE_FL_EOS))
Willy Tarreau51cd5952020-06-05 12:25:38 +02001552 goto end_recv;
1553
Christopher Faulet147b8c92021-04-10 09:00:38 +02001554 if (check->state & CHK_ST_IN_ALLOC) {
1555 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001556 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001557 }
Christopher Fauletb381a502020-11-25 13:47:00 +01001558
1559 if (!check_get_buf(check, &check->bi)) {
1560 check->state |= CHK_ST_IN_ALLOC;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001561 TRACE_STATE("waiting for input buffer allocation", CHK_EV_RX_DATA|CHK_EV_RX_BLK, check);
Christopher Fauletb381a502020-11-25 13:47:00 +01001562 goto wait_more_data;
1563 }
1564
Willy Tarreau4596fe22022-05-17 19:07:51 +02001565 /* errors on the connection and the stream connector were already checked */
Willy Tarreau51cd5952020-06-05 12:25:38 +02001566
1567 /* prepare to detect if the mux needs more room */
Willy Tarreaub605c422022-05-17 17:04:55 +02001568 sc_ep_clr(cs, SE_FL_WANT_ROOM);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001569
Willy Tarreaub605c422022-05-17 17:04:55 +02001570 while (sc_ep_test(cs, SE_FL_RCV_MORE) ||
1571 (!(conn->flags & CO_FL_ERROR) && !sc_ep_test(cs, SE_FL_ERROR | SE_FL_EOS))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001572 max = (IS_HTX_CS(cs) ? htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
1573 read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
1574 cur_read += read;
1575 if (!read ||
Willy Tarreaub605c422022-05-17 17:04:55 +02001576 sc_ep_test(cs, SE_FL_WANT_ROOM) ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02001577 (--read_poll <= 0) ||
1578 (read < max && read >= global.tune.recv_enough))
1579 break;
1580 }
1581
1582 end_recv:
1583 is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreaub605c422022-05-17 17:04:55 +02001584 if (is_empty && ((conn->flags & CO_FL_ERROR) || sc_ep_test(cs, SE_FL_ERROR))) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02001585 /* Report network errors only if we got no other data. Otherwise
1586 * we'll let the upper layers decide whether the response is OK
1587 * or not. It is very common that an RST sent by the server is
1588 * reported as an error just after the last data chunk.
1589 */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001590 TRACE_ERROR("connection error during recv", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001591 goto stop;
1592 }
1593 if (!cur_read) {
Willy Tarreaub605c422022-05-17 17:04:55 +02001594 if (sc_ep_test(cs, SE_FL_EOI)) {
Christopher Fauletd16e7dd2021-10-20 13:53:38 +02001595 /* If EOI is set, it means there is a response or an error */
1596 goto out;
1597 }
Willy Tarreau0cfcc402022-05-17 16:10:17 +02001598
Willy Tarreaub605c422022-05-17 17:04:55 +02001599 if (!sc_ep_test(cs, SE_FL_WANT_ROOM | SE_FL_ERROR | SE_FL_EOS)) {
Christopher Fauletc95eaef2022-05-18 15:57:15 +02001600 conn->mux->subscribe(cs, SUB_RETRY_RECV, &cs->wait_event);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001601 TRACE_DEVEL("waiting for response", CHK_EV_RX_DATA, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001602 goto wait_more_data;
1603 }
Willy Tarreau0cfcc402022-05-17 16:10:17 +02001604
Willy Tarreau51cd5952020-06-05 12:25:38 +02001605 if (is_empty) {
1606 int status;
1607
1608 chunk_printf(&trash, "TCPCHK got an empty response at step %d",
1609 tcpcheck_get_step_id(check, rule));
1610 if (rule->comment)
1611 chunk_appendf(&trash, " comment: '%s'", rule->comment);
1612
Christopher Faulet147b8c92021-04-10 09:00:38 +02001613 TRACE_ERROR("empty response", CHK_EV_RX_DATA|CHK_EV_RX_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001614 status = ((rule->expect.err_status != HCHK_STATUS_UNKNOWN) ? rule->expect.err_status : HCHK_STATUS_L7RSP);
1615 set_server_check_status(check, status, trash.area);
1616 goto stop;
1617 }
1618 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001619 TRACE_DATA("data received", CHK_EV_RX_DATA, check, 0, 0, (size_t[]){cur_read});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001620
1621 out:
Christopher Fauletb381a502020-11-25 13:47:00 +01001622 if (!b_data(&check->bi) || ret == TCPCHK_EVAL_STOP)
1623 check_release_buf(check, &check->bi);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001624
1625 TRACE_LEAVE(CHK_EV_RX_DATA, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001626 return ret;
1627
1628 stop:
1629 ret = TCPCHK_EVAL_STOP;
1630 goto out;
1631
1632 wait_more_data:
1633 ret = TCPCHK_EVAL_WAIT;
1634 goto out;
1635}
1636
1637/* Evaluates an HTTP TCPCHK_ACT_EXPECT rule. If <last_read> is set , no more data
1638 * are expected. Returns TCPCHK_EVAL_WAIT to wait for more data,
1639 * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an
1640 * error occurred.
1641 */
1642enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
1643{
1644 struct htx *htx = htxbuf(&check->bi);
1645 struct htx_sl *sl;
1646 struct htx_blk *blk;
1647 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1648 struct tcpcheck_expect *expect = &rule->expect;
1649 struct buffer *msg = NULL, *tmp = NULL, *nbuf = NULL, *vbuf = NULL;
1650 enum healthcheck_status status = HCHK_STATUS_L7RSP;
1651 struct ist desc = IST_NULL;
1652 int i, match, inverse;
1653
Christopher Faulet147b8c92021-04-10 09:00:38 +02001654 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1655
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001656 last_read |= (!htx_free_data_space(htx) || (htx->flags & HTX_FL_EOM));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001657
1658 if (htx->flags & HTX_FL_PARSING_ERROR) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001659 TRACE_ERROR("invalid response", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001660 status = HCHK_STATUS_L7RSP;
1661 goto error;
1662 }
1663
1664 if (htx_is_empty(htx)) {
1665 if (last_read) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001666 TRACE_ERROR("empty response received", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001667 status = HCHK_STATUS_L7RSP;
1668 goto error;
1669 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02001670 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001671 goto wait_more_data;
1672 }
1673
1674 sl = http_get_stline(htx);
1675 check->code = sl->info.res.status;
1676
1677 if (check->server &&
1678 (check->server->proxy->options & PR_O_DISABLE404) &&
1679 (check->server->next_state != SRV_ST_STOPPED) &&
1680 (check->code == 404)) {
1681 /* 404 may be accepted as "stopping" only if the server was up */
Christopher Faulet147b8c92021-04-10 09:00:38 +02001682 TRACE_STATE("404 response & disable-404", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001683 goto out;
1684 }
1685
1686 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1687 /* Make GCC happy ; initialize match to a failure state. */
1688 match = inverse;
1689 status = expect->err_status;
1690
1691 switch (expect->type) {
1692 case TCPCHK_EXPECT_HTTP_STATUS:
1693 match = 0;
1694 for (i = 0; i < expect->codes.num; i++) {
1695 if (sl->info.res.status >= expect->codes.codes[i][0] &&
1696 sl->info.res.status <= expect->codes.codes[i][1]) {
1697 match = 1;
1698 break;
1699 }
1700 }
1701
1702 /* Set status and description in case of error */
1703 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1704 if (LIST_ISEMPTY(&expect->onerror_fmt))
1705 desc = htx_sl_res_reason(sl);
1706 break;
1707 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
1708 match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
1709
1710 /* Set status and description in case of error */
1711 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1712 if (LIST_ISEMPTY(&expect->onerror_fmt))
1713 desc = htx_sl_res_reason(sl);
1714 break;
1715
1716 case TCPCHK_EXPECT_HTTP_HEADER: {
1717 struct http_hdr_ctx ctx;
1718 struct ist npat, vpat, value;
1719 int full = (expect->flags & (TCPCHK_EXPT_FL_HTTP_HVAL_NONE|TCPCHK_EXPT_FL_HTTP_HVAL_FULL));
1720
1721 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
1722 nbuf = alloc_trash_chunk();
1723 if (!nbuf) {
1724 status = HCHK_STATUS_L7RSP;
1725 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001726 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001727 goto error;
1728 }
1729 nbuf->data = sess_build_logline(check->sess, NULL, b_orig(nbuf), b_size(nbuf), &expect->hdr.name_fmt);
1730 if (!b_data(nbuf)) {
1731 status = HCHK_STATUS_L7RSP;
1732 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001733 TRACE_ERROR("invalid log-format string (hdr name)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001734 goto error;
1735 }
1736 npat = ist2(b_orig(nbuf), b_data(nbuf));
1737 }
1738 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG))
1739 npat = expect->hdr.name;
1740
1741 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
1742 vbuf = alloc_trash_chunk();
1743 if (!vbuf) {
1744 status = HCHK_STATUS_L7RSP;
1745 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001746 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001747 goto error;
1748 }
1749 vbuf->data = sess_build_logline(check->sess, NULL, b_orig(vbuf), b_size(vbuf), &expect->hdr.value_fmt);
1750 if (!b_data(vbuf)) {
1751 status = HCHK_STATUS_L7RSP;
1752 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001753 TRACE_ERROR("invalid log-format string (hdr value)", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001754 goto error;
1755 }
1756 vpat = ist2(b_orig(vbuf), b_data(vbuf));
1757 }
1758 else if (!(expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG))
1759 vpat = expect->hdr.value;
1760
1761 match = 0;
1762 ctx.blk = NULL;
1763 while (1) {
1764 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HNAME_TYPE) {
1765 case TCPCHK_EXPT_FL_HTTP_HNAME_STR:
1766 if (!http_find_str_header(htx, npat, &ctx, full))
1767 goto end_of_match;
1768 break;
1769 case TCPCHK_EXPT_FL_HTTP_HNAME_BEG:
1770 if (!http_find_pfx_header(htx, npat, &ctx, full))
1771 goto end_of_match;
1772 break;
1773 case TCPCHK_EXPT_FL_HTTP_HNAME_END:
1774 if (!http_find_sfx_header(htx, npat, &ctx, full))
1775 goto end_of_match;
1776 break;
1777 case TCPCHK_EXPT_FL_HTTP_HNAME_SUB:
1778 if (!http_find_sub_header(htx, npat, &ctx, full))
1779 goto end_of_match;
1780 break;
1781 case TCPCHK_EXPT_FL_HTTP_HNAME_REG:
1782 if (!http_match_header(htx, expect->hdr.name_re, &ctx, full))
1783 goto end_of_match;
1784 break;
1785 default:
1786 /* should never happen */
1787 goto end_of_match;
1788 }
1789
1790 /* A header has matched the name pattern, let's test its
1791 * value now (always defined from there). If there is no
1792 * value pattern, it is a good match.
1793 */
1794
1795 if (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
1796 match = 1;
1797 goto end_of_match;
1798 }
1799
1800 value = ctx.value;
1801 switch (expect->flags & TCPCHK_EXPT_FL_HTTP_HVAL_TYPE) {
1802 case TCPCHK_EXPT_FL_HTTP_HVAL_STR:
1803 if (isteq(value, vpat)) {
1804 match = 1;
1805 goto end_of_match;
1806 }
1807 break;
1808 case TCPCHK_EXPT_FL_HTTP_HVAL_BEG:
1809 if (istlen(value) < istlen(vpat))
1810 break;
1811 value = ist2(istptr(value), istlen(vpat));
1812 if (isteq(value, vpat)) {
1813 match = 1;
1814 goto end_of_match;
1815 }
1816 break;
1817 case TCPCHK_EXPT_FL_HTTP_HVAL_END:
1818 if (istlen(value) < istlen(vpat))
1819 break;
Tim Duesterhus4c8f75f2021-11-06 15:14:44 +01001820 value = ist2(istend(value) - istlen(vpat), istlen(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02001821 if (isteq(value, vpat)) {
1822 match = 1;
1823 goto end_of_match;
1824 }
1825 break;
1826 case TCPCHK_EXPT_FL_HTTP_HVAL_SUB:
1827 if (isttest(istist(value, vpat))) {
1828 match = 1;
1829 goto end_of_match;
1830 }
1831 break;
1832 case TCPCHK_EXPT_FL_HTTP_HVAL_REG:
1833 if (regex_exec2(expect->hdr.value_re, istptr(value), istlen(value))) {
1834 match = 1;
1835 goto end_of_match;
1836 }
1837 break;
1838 }
1839 }
1840
1841 end_of_match:
1842 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7STS);
1843 if (LIST_ISEMPTY(&expect->onerror_fmt))
1844 desc = htx_sl_res_reason(sl);
1845 break;
1846 }
1847
1848 case TCPCHK_EXPECT_HTTP_BODY:
1849 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
1850 case TCPCHK_EXPECT_HTTP_BODY_LF:
1851 match = 0;
1852 chunk_reset(&trash);
1853 for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
1854 enum htx_blk_type type = htx_get_blk_type(blk);
1855
Christopher Fauletd1ac2b92020-12-02 19:12:22 +01001856 if (type == HTX_BLK_TLR || type == HTX_BLK_EOT)
Willy Tarreau51cd5952020-06-05 12:25:38 +02001857 break;
1858 if (type == HTX_BLK_DATA) {
1859 if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
1860 break;
1861 }
1862 }
1863
1864 if (!b_data(&trash)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02001865 if (!last_read) {
1866 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001867 goto wait_more_data;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001868 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02001869 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1870 if (LIST_ISEMPTY(&expect->onerror_fmt))
1871 desc = ist("HTTP content check could not find a response body");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001872 TRACE_ERROR("no response boduy found while expected", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001873 goto error;
1874 }
1875
1876 if (expect->type == TCPCHK_EXPECT_HTTP_BODY_LF) {
1877 tmp = alloc_trash_chunk();
1878 if (!tmp) {
1879 status = HCHK_STATUS_L7RSP;
1880 desc = ist("Failed to allocate buffer to eval log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001881 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001882 goto error;
1883 }
1884 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
1885 if (!b_data(tmp)) {
1886 status = HCHK_STATUS_L7RSP;
1887 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02001888 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001889 goto error;
1890 }
1891 }
1892
1893 if (!last_read &&
1894 ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
1895 ((expect->type == TCPCHK_EXPECT_HTTP_BODY_LF && b_data(&trash) < b_data(tmp))) ||
1896 (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
1897 ret = TCPCHK_EVAL_WAIT;
1898 goto out;
1899 }
1900
1901 if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
1902 match = my_memmem(b_orig(&trash), b_data(&trash), istptr(expect->data), istlen(expect->data)) != NULL;
1903 else if (expect->type ==TCPCHK_EXPECT_HTTP_BODY_LF)
1904 match = my_memmem(b_orig(&trash), b_data(&trash), b_orig(tmp), b_data(tmp)) != NULL;
1905 else
1906 match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
1907
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001908 /* Wait for more data on mismatch only if no minimum is defined (-1),
1909 * otherwise the absence of match is already conclusive.
1910 */
1911 if (!match && !last_read && (expect->min_recv == -1)) {
1912 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001913 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Christopher Fauletcad5f5e2020-12-09 18:45:47 +01001914 goto out;
1915 }
1916
Willy Tarreau51cd5952020-06-05 12:25:38 +02001917 /* Set status and description in case of error */
1918 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1919 if (LIST_ISEMPTY(&expect->onerror_fmt))
1920 desc = (inverse
1921 ? ist("HTTP check matched unwanted content")
1922 : ist("HTTP content check did not match"));
1923 break;
1924
1925
1926 default:
1927 /* should never happen */
1928 status = ((status != HCHK_STATUS_UNKNOWN) ? status : HCHK_STATUS_L7RSP);
1929 goto error;
1930 }
1931
Christopher Faulet147b8c92021-04-10 09:00:38 +02001932 if (!(match ^ inverse)) {
1933 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001934 goto error;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001935 }
1936
1937 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001938
1939 out:
1940 free_trash_chunk(tmp);
1941 free_trash_chunk(nbuf);
1942 free_trash_chunk(vbuf);
1943 free_trash_chunk(msg);
Christopher Faulet147b8c92021-04-10 09:00:38 +02001944 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02001945 return ret;
1946
1947 error:
Christopher Faulet147b8c92021-04-10 09:00:38 +02001948 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001949 ret = TCPCHK_EVAL_STOP;
1950 msg = alloc_trash_chunk();
1951 if (msg)
1952 tcpcheck_expect_onerror_message(msg, check, rule, 0, desc);
1953 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
1954 goto out;
1955
1956 wait_more_data:
1957 ret = TCPCHK_EVAL_WAIT;
1958 goto out;
1959}
1960
1961/* Evaluates a TCP TCPCHK_ACT_EXPECT rule. Returns TCPCHK_EVAL_WAIT to wait for
1962 * more data, TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP
1963 * if an error occurred.
1964 */
1965enum tcpcheck_eval_ret tcpcheck_eval_expect(struct check *check, struct tcpcheck_rule *rule, int last_read)
1966{
1967 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
1968 struct tcpcheck_expect *expect = &rule->expect;
1969 struct buffer *msg = NULL, *tmp = NULL;
1970 struct ist desc = IST_NULL;
1971 enum healthcheck_status status;
1972 int match, inverse;
1973
Christopher Faulet147b8c92021-04-10 09:00:38 +02001974 TRACE_ENTER(CHK_EV_TCPCHK_EXP, check);
1975
Willy Tarreau51cd5952020-06-05 12:25:38 +02001976 last_read |= b_full(&check->bi);
1977
1978 /* The current expect might need more data than the previous one, check again
1979 * that the minimum amount data required to match is respected.
1980 */
1981 if (!last_read) {
1982 if ((expect->type == TCPCHK_EXPECT_STRING || expect->type == TCPCHK_EXPECT_BINARY) &&
1983 (b_data(&check->bi) < istlen(expect->data))) {
1984 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001985 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001986 goto out;
1987 }
1988 if (expect->min_recv > 0 && (b_data(&check->bi) < expect->min_recv)) {
1989 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02001990 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02001991 goto out;
1992 }
1993 }
1994
1995 inverse = !!(expect->flags & TCPCHK_EXPT_FL_INV);
1996 /* Make GCC happy ; initialize match to a failure state. */
1997 match = inverse;
1998 status = ((expect->err_status != HCHK_STATUS_UNKNOWN) ? expect->err_status : HCHK_STATUS_L7RSP);
1999
2000 switch (expect->type) {
2001 case TCPCHK_EXPECT_STRING:
2002 case TCPCHK_EXPECT_BINARY:
2003 match = my_memmem(b_head(&check->bi), b_data(&check->bi), istptr(expect->data), istlen(expect->data)) != NULL;
2004 break;
2005 case TCPCHK_EXPECT_STRING_REGEX:
2006 match = regex_exec2(expect->regex, b_head(&check->bi), MIN(b_data(&check->bi), b_size(&check->bi)-1));
2007 break;
2008
2009 case TCPCHK_EXPECT_BINARY_REGEX:
2010 chunk_reset(&trash);
2011 dump_binary(&trash, b_head(&check->bi), b_data(&check->bi));
2012 match = regex_exec2(expect->regex, b_head(&trash), MIN(b_data(&trash), b_size(&trash)-1));
2013 break;
2014
2015 case TCPCHK_EXPECT_STRING_LF:
2016 case TCPCHK_EXPECT_BINARY_LF:
2017 match = 0;
2018 tmp = alloc_trash_chunk();
2019 if (!tmp) {
2020 status = HCHK_STATUS_L7RSP;
2021 desc = ist("Failed to allocate buffer to eval format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002022 TRACE_ERROR("buffer allocation failure", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002023 goto error;
2024 }
2025 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &expect->fmt);
2026 if (!b_data(tmp)) {
2027 status = HCHK_STATUS_L7RSP;
2028 desc = ist("log-format string evaluated to an empty string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002029 TRACE_ERROR("invalid log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002030 goto error;
2031 }
2032 if (expect->type == TCPCHK_EXPECT_BINARY_LF) {
2033 int len = tmp->data;
2034 if (parse_binary(b_orig(tmp), &tmp->area, &len, NULL) == 0) {
2035 status = HCHK_STATUS_L7RSP;
2036 desc = ist("Failed to parse hexastring resulting of eval of a log-format string");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002037 TRACE_ERROR("invalid binary log-format string", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002038 goto error;
2039 }
2040 tmp->data = len;
2041 }
2042 if (b_data(&check->bi) < tmp->data) {
2043 if (!last_read) {
2044 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002045 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002046 goto out;
2047 }
2048 break;
2049 }
2050 match = my_memmem(b_head(&check->bi), b_data(&check->bi), b_orig(tmp), b_data(tmp)) != NULL;
2051 break;
2052
2053 case TCPCHK_EXPECT_CUSTOM:
2054 if (expect->custom)
2055 ret = expect->custom(check, rule, last_read);
2056 goto out;
2057 default:
2058 /* Should never happen. */
2059 ret = TCPCHK_EVAL_STOP;
2060 goto out;
2061 }
2062
2063
2064 /* Wait for more data on mismatch only if no minimum is defined (-1),
2065 * otherwise the absence of match is already conclusive.
2066 */
2067 if (!match && !last_read && (expect->min_recv == -1)) {
2068 ret = TCPCHK_EVAL_WAIT;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002069 TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002070 goto out;
2071 }
2072
2073 /* Result as expected, next rule. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002074 if (match ^ inverse) {
2075 TRACE_STATE("expect rule succeeded", CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002076 goto out;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002077 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002078
2079 error:
2080 /* From this point on, we matched something we did not want, this is an error state. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002081 TRACE_STATE("expect rule failed", CHK_EV_TCPCHK_EXP|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002082 ret = TCPCHK_EVAL_STOP;
2083 msg = alloc_trash_chunk();
2084 if (msg)
2085 tcpcheck_expect_onerror_message(msg, check, rule, match, desc);
2086 set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
2087 free_trash_chunk(msg);
2088
2089 out:
2090 free_trash_chunk(tmp);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002091 TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret});
Willy Tarreau51cd5952020-06-05 12:25:38 +02002092 return ret;
2093}
2094
2095/* Evaluates a TCPCHK_ACT_ACTION_KW rule. Returns TCPCHK_EVAL_CONTINUE to
2096 * evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. It never
2097 * waits.
2098 */
2099enum tcpcheck_eval_ret tcpcheck_eval_action_kw(struct check *check, struct tcpcheck_rule *rule)
2100{
2101 enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
2102 struct act_rule *act_rule;
2103 enum act_return act_ret;
2104
2105 act_rule =rule->action_kw.rule;
2106 act_ret = act_rule->action_ptr(act_rule, check->proxy, check->sess, NULL, 0);
2107 if (act_ret != ACT_RET_CONT) {
2108 chunk_printf(&trash, "TCPCHK ACTION unexpected result at step %d\n",
2109 tcpcheck_get_step_id(check, rule));
2110 set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
2111 ret = TCPCHK_EVAL_STOP;
2112 }
2113
2114 return ret;
2115}
2116
2117/* Executes a tcp-check ruleset. Note that this is called both from the
2118 * connection's wake() callback and from the check scheduling task. It returns
2119 * 0 on normal cases, or <0 if a close() has happened on an existing connection,
2120 * presenting the risk of an fd replacement.
2121 *
2122 * Please do NOT place any return statement in this function and only leave
2123 * via the out_end_tcpcheck label after setting retcode.
2124 */
2125int tcpcheck_main(struct check *check)
2126{
2127 struct tcpcheck_rule *rule;
Willy Tarreau4596fe22022-05-17 19:07:51 +02002128 struct stconn *cs = check->cs;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002129 struct connection *conn = cs_conn(cs);
2130 int must_read = 1, last_read = 0;
Christopher Faulet39066c22020-11-25 13:34:51 +01002131 int retcode = 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002132 enum tcpcheck_eval_ret eval_ret;
2133
2134 /* here, we know that the check is complete or that it failed */
2135 if (check->result != CHK_RES_UNKNOWN)
2136 goto out;
2137
Christopher Faulet147b8c92021-04-10 09:00:38 +02002138 TRACE_ENTER(CHK_EV_TCPCHK_EVAL, check);
2139
Willy Tarreau4596fe22022-05-17 19:07:51 +02002140 /* Note: the stream connector and the connection may only be undefined before
Willy Tarreau51cd5952020-06-05 12:25:38 +02002141 * the first rule evaluation (it is always a connect rule) or when the
Willy Tarreau4596fe22022-05-17 19:07:51 +02002142 * stream connector allocation failed on a connect rule, during cs allocation.
Willy Tarreau51cd5952020-06-05 12:25:38 +02002143 */
2144
2145 /* 1- check for connection error, if any */
Willy Tarreaub605c422022-05-17 17:04:55 +02002146 if ((conn && conn->flags & CO_FL_ERROR) || sc_ep_test(cs, SE_FL_ERROR))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002147 goto out_end_tcpcheck;
2148
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002149 /* 2- check if a rule must be resume. It happens if check->current_step
Willy Tarreau51cd5952020-06-05 12:25:38 +02002150 * is defined. */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002151 else if (check->current_step) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002152 rule = check->current_step;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002153 TRACE_PROTO("resume rule evaluation", CHK_EV_TCPCHK_EVAL, check, 0, 0, (size_t[]){ tcpcheck_get_step_id(check, rule)});
2154 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002155
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002156 /* 3- It is the first evaluation. We must create a session and preset
Willy Tarreau51cd5952020-06-05 12:25:38 +02002157 * tcp-check variables */
2158 else {
2159 struct tcpcheck_var *var;
2160
2161 /* First evaluation, create a session */
2162 check->sess = session_new(&checks_fe, NULL, &check->obj_type);
2163 if (!check->sess) {
2164 chunk_printf(&trash, "TCPCHK error allocating check session");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002165 TRACE_ERROR("session allocation failure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002166 set_server_check_status(check, HCHK_STATUS_SOCKERR, trash.area);
2167 goto out_end_tcpcheck;
2168 }
Willy Tarreaub7bfcb32021-08-31 08:13:25 +02002169 vars_init_head(&check->vars, SCOPE_CHECK);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002170 rule = LIST_NEXT(check->tcpcheck_rules->list, typeof(rule), list);
2171
2172 /* Preset tcp-check variables */
2173 list_for_each_entry(var, &check->tcpcheck_rules->preset_vars, list) {
2174 struct sample smp;
2175
2176 memset(&smp, 0, sizeof(smp));
2177 smp_set_owner(&smp, check->proxy, check->sess, NULL, SMP_OPT_FINAL);
2178 smp.data = var->data;
2179 vars_set_by_name_ifexist(istptr(var->name), istlen(var->name), &smp);
2180 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002181 TRACE_PROTO("start rules evaluation", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002182 }
2183
2184 /* Now evaluate the tcp-check rules */
2185
2186 list_for_each_entry_from(rule, check->tcpcheck_rules->list, list) {
2187 check->code = 0;
2188 switch (rule->action) {
2189 case TCPCHK_ACT_CONNECT:
Christopher Faulet8f100422021-01-18 15:47:03 +01002190 /* Not the first connection, release it first */
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002191 if (cs_conn(cs) && check->current_step != rule) {
Christopher Faulet8f100422021-01-18 15:47:03 +01002192 check->state |= CHK_ST_CLOSE_CONN;
2193 retcode = -1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002194 }
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002195
2196 check->current_step = rule;
Christopher Faulet8f100422021-01-18 15:47:03 +01002197
2198 /* We are still waiting the connection gets closed */
2199 if (cs && (check->state & CHK_ST_CLOSE_CONN)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002200 TRACE_DEVEL("wait previous connection closure", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Christopher Faulet8f100422021-01-18 15:47:03 +01002201 eval_ret = TCPCHK_EVAL_WAIT;
2202 break;
2203 }
2204
Christopher Faulet147b8c92021-04-10 09:00:38 +02002205 TRACE_PROTO("eval connect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_CONN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002206 eval_ret = tcpcheck_eval_connect(check, rule);
2207
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002208 /* Refresh connection */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002209 conn = cs_conn(cs);
Christopher Fauletb1bb0692020-11-25 16:47:30 +01002210 last_read = 0;
Christopher Faulet54e85cb2022-01-06 08:46:56 +01002211 must_read = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002212 break;
2213 case TCPCHK_ACT_SEND:
2214 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002215 TRACE_PROTO("eval send rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_SND, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002216 eval_ret = tcpcheck_eval_send(check, rule);
2217 must_read = 1;
2218 break;
2219 case TCPCHK_ACT_EXPECT:
2220 check->current_step = rule;
Christopher Faulet147b8c92021-04-10 09:00:38 +02002221 TRACE_PROTO("eval expect rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_EXP, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002222 if (must_read) {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002223 eval_ret = tcpcheck_eval_recv(check, rule);
2224 if (eval_ret == TCPCHK_EVAL_STOP)
2225 goto out_end_tcpcheck;
2226 else if (eval_ret == TCPCHK_EVAL_WAIT)
2227 goto out;
Willy Tarreaub605c422022-05-17 17:04:55 +02002228 last_read = ((conn->flags & CO_FL_ERROR) || sc_ep_test(cs, SE_FL_ERROR | SE_FL_EOS));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002229 must_read = 0;
2230 }
2231
2232 eval_ret = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
2233 ? tcpcheck_eval_expect_http(check, rule, last_read)
2234 : tcpcheck_eval_expect(check, rule, last_read));
2235
2236 if (eval_ret == TCPCHK_EVAL_WAIT) {
2237 check->current_step = rule->expect.head;
Christopher Fauletc95eaef2022-05-18 15:57:15 +02002238 if (!(cs->wait_event.events & SUB_RETRY_RECV))
2239 conn->mux->subscribe(cs, SUB_RETRY_RECV, &cs->wait_event);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002240 }
2241 break;
2242 case TCPCHK_ACT_ACTION_KW:
2243 /* Don't update the current step */
Christopher Faulet147b8c92021-04-10 09:00:38 +02002244 TRACE_PROTO("eval action kw rule", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ACT, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002245 eval_ret = tcpcheck_eval_action_kw(check, rule);
2246 break;
2247 default:
2248 /* Otherwise, just go to the next one and don't update
2249 * the current step
2250 */
2251 eval_ret = TCPCHK_EVAL_CONTINUE;
2252 break;
2253 }
2254
2255 switch (eval_ret) {
2256 case TCPCHK_EVAL_CONTINUE:
2257 break;
2258 case TCPCHK_EVAL_WAIT:
2259 goto out;
2260 case TCPCHK_EVAL_STOP:
2261 goto out_end_tcpcheck;
2262 }
2263 }
2264
2265 /* All rules was evaluated */
2266 if (check->current_step) {
2267 rule = check->current_step;
2268
Christopher Faulet147b8c92021-04-10 09:00:38 +02002269 TRACE_DEVEL("eval tcp-check result", CHK_EV_TCPCHK_EVAL, check);
2270
Willy Tarreau51cd5952020-06-05 12:25:38 +02002271 if (rule->action == TCPCHK_ACT_EXPECT) {
2272 struct buffer *msg;
2273 enum healthcheck_status status;
2274
2275 if (check->server &&
2276 (check->server->proxy->options & PR_O_DISABLE404) &&
2277 (check->server->next_state != SRV_ST_STOPPED) &&
2278 (check->code == 404)) {
2279 set_server_check_status(check, HCHK_STATUS_L7OKCD, NULL);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002280 TRACE_PROTO("tcp-check conditionally passed (disable-404)", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002281 goto out_end_tcpcheck;
2282 }
2283
2284 msg = alloc_trash_chunk();
2285 if (msg)
2286 tcpcheck_expect_onsuccess_message(msg, check, rule, IST_NULL);
2287 status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD);
2288 set_server_check_status(check, status, (msg ? b_head(msg) : "(tcp-check)"));
2289 free_trash_chunk(msg);
2290 }
2291 else if (rule->action == TCPCHK_ACT_CONNECT) {
2292 const char *msg = ((rule->connect.options & TCPCHK_OPT_IMPLICIT) ? NULL : "(tcp-check)");
2293 enum healthcheck_status status = HCHK_STATUS_L4OK;
2294#ifdef USE_OPENSSL
Willy Tarreau1057bee2021-10-06 11:38:44 +02002295 if (conn_is_ssl(conn))
Willy Tarreau51cd5952020-06-05 12:25:38 +02002296 status = HCHK_STATUS_L6OK;
2297#endif
2298 set_server_check_status(check, status, msg);
2299 }
Christopher Faulet8d4977a2021-01-05 16:56:07 +01002300 else
2301 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Willy Tarreau51cd5952020-06-05 12:25:38 +02002302 }
Christopher Faulet147b8c92021-04-10 09:00:38 +02002303 else {
Willy Tarreau51cd5952020-06-05 12:25:38 +02002304 set_server_check_status(check, HCHK_STATUS_L7OKD, "(tcp-check)");
Christopher Faulet147b8c92021-04-10 09:00:38 +02002305 }
2306 TRACE_PROTO("tcp-check passed", CHK_EV_TCPCHK_EVAL, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002307
2308 out_end_tcpcheck:
Willy Tarreaub605c422022-05-17 17:04:55 +02002309 if ((conn && conn->flags & CO_FL_ERROR) || sc_ep_test(cs, SE_FL_ERROR)) {
Christopher Faulet147b8c92021-04-10 09:00:38 +02002310 TRACE_ERROR("report connection error", CHK_EV_TCPCHK_EVAL|CHK_EV_TCPCHK_ERR, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002311 chk_report_conn_err(check, errno, 0);
Christopher Faulet147b8c92021-04-10 09:00:38 +02002312 }
Willy Tarreau51cd5952020-06-05 12:25:38 +02002313
Christopher Fauletb381a502020-11-25 13:47:00 +01002314 /* the tcpcheck is finished, release in/out buffer now */
2315 check_release_buf(check, &check->bi);
2316 check_release_buf(check, &check->bo);
2317
Willy Tarreau51cd5952020-06-05 12:25:38 +02002318 out:
Christopher Faulet147b8c92021-04-10 09:00:38 +02002319 TRACE_LEAVE(CHK_EV_HCHK_RUN, check);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002320 return retcode;
2321}
2322
Willy Tarreaua631b862022-03-02 14:54:44 +01002323void tcp_check_keywords_register(struct action_kw_list *kw_list)
2324{
2325 LIST_APPEND(&tcp_check_keywords.list, &kw_list->list);
2326}
Willy Tarreau51cd5952020-06-05 12:25:38 +02002327
2328/**************************************************************************/
2329/******************* Internals to parse tcp-check rules *******************/
2330/**************************************************************************/
2331struct action_kw_list tcp_check_keywords = {
2332 .list = LIST_HEAD_INIT(tcp_check_keywords.list),
2333};
2334
2335/* Creates a tcp-check rule resulting from parsing a custom keyword. NULL is
2336 * returned on error.
2337 */
2338struct tcpcheck_rule *parse_tcpcheck_action(char **args, int cur_arg, struct proxy *px,
2339 struct list *rules, struct action_kw *kw,
2340 const char *file, int line, char **errmsg)
2341{
2342 struct tcpcheck_rule *chk = NULL;
2343 struct act_rule *actrule = NULL;
2344
Willy Tarreaud535f802021-10-11 08:49:26 +02002345 actrule = new_act_rule(ACT_F_TCP_CHK, file, line);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002346 if (!actrule) {
2347 memprintf(errmsg, "out of memory");
2348 goto error;
2349 }
2350 actrule->kw = kw;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002351
2352 cur_arg++;
2353 if (kw->parse((const char **)args, &cur_arg, px, actrule, errmsg) == ACT_RET_PRS_ERR) {
2354 memprintf(errmsg, "'%s' : %s", kw->kw, *errmsg);
2355 goto error;
2356 }
2357
2358 chk = calloc(1, sizeof(*chk));
2359 if (!chk) {
2360 memprintf(errmsg, "out of memory");
2361 goto error;
2362 }
2363 chk->action = TCPCHK_ACT_ACTION_KW;
2364 chk->action_kw.rule = actrule;
2365 return chk;
2366
2367 error:
2368 free(actrule);
2369 return NULL;
2370}
2371
2372/* Parses and creates a tcp-check connect or an http-check connect rule. NULL is
2373 * returned on error.
2374 */
2375struct tcpcheck_rule *parse_tcpcheck_connect(char **args, int cur_arg, struct proxy *px, struct list *rules,
2376 const char *file, int line, char **errmsg)
2377{
2378 struct tcpcheck_rule *chk = NULL;
2379 struct sockaddr_storage *sk = NULL;
2380 char *comment = NULL, *sni = NULL, *alpn = NULL;
2381 struct sample_expr *port_expr = NULL;
2382 const struct mux_proto_list *mux_proto = NULL;
2383 unsigned short conn_opts = 0;
2384 long port = 0;
2385 int alpn_len = 0;
2386
2387 list_for_each_entry(chk, rules, list) {
2388 if (chk->action == TCPCHK_ACT_CONNECT)
2389 break;
2390 if (chk->action == TCPCHK_ACT_COMMENT ||
2391 chk->action == TCPCHK_ACT_ACTION_KW ||
2392 (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
2393 continue;
2394
2395 memprintf(errmsg, "first step MUST also be a 'connect', "
2396 "optionally preceded by a 'set-var', an 'unset-var' or a 'comment', "
2397 "when there is a 'connect' step in the tcp-check ruleset");
2398 goto error;
2399 }
2400
2401 cur_arg++;
2402 while (*(args[cur_arg])) {
2403 if (strcmp(args[cur_arg], "default") == 0)
2404 conn_opts |= TCPCHK_OPT_DEFAULT_CONNECT;
2405 else if (strcmp(args[cur_arg], "addr") == 0) {
2406 int port1, port2;
Willy Tarreau51cd5952020-06-05 12:25:38 +02002407
2408 if (!*(args[cur_arg+1])) {
2409 memprintf(errmsg, "'%s' expects <ipv4|ipv6> as argument.", args[cur_arg]);
2410 goto error;
2411 }
2412
Willy Tarreau65ec4e32020-09-16 19:17:08 +02002413 sk = str2sa_range(args[cur_arg+1], NULL, &port1, &port2, NULL, NULL,
2414 errmsg, NULL, NULL, PA_O_RESOLVE | PA_O_PORT_OK | PA_O_STREAM | PA_O_CONNECT);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002415 if (!sk) {
2416 memprintf(errmsg, "'%s' : %s.", args[cur_arg], *errmsg);
2417 goto error;
2418 }
2419
Willy Tarreau51cd5952020-06-05 12:25:38 +02002420 cur_arg++;
2421 }
2422 else if (strcmp(args[cur_arg], "port") == 0) {
2423 const char *p, *end;
2424
2425 if (!*(args[cur_arg+1])) {
2426 memprintf(errmsg, "'%s' expects a port number or a sample expression as argument.", args[cur_arg]);
2427 goto error;
2428 }
2429 cur_arg++;
2430
2431 port = 0;
2432 release_sample_expr(port_expr);
2433 p = args[cur_arg]; end = p + strlen(p);
2434 port = read_uint(&p, end);
2435 if (p != end) {
2436 int idx = 0;
2437
2438 px->conf.args.ctx = ARGC_SRV;
2439 port_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02002440 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002441
2442 if (!port_expr) {
2443 memprintf(errmsg, "error detected while parsing port expression : %s", *errmsg);
2444 goto error;
2445 }
2446 if (!(port_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
2447 memprintf(errmsg, "error detected while parsing port expression : "
2448 " fetch method '%s' extracts information from '%s', "
2449 "none of which is available here.\n",
2450 args[cur_arg], sample_src_names(port_expr->fetch->use));
2451 goto error;
2452 }
2453 px->http_needed |= !!(port_expr->fetch->use & SMP_USE_HTTP_ANY);
2454 }
2455 else if (port > 65535 || port < 1) {
2456 memprintf(errmsg, "expects a valid TCP port (from range 1 to 65535) or a sample expression, got %s.",
2457 args[cur_arg]);
2458 goto error;
2459 }
2460 }
2461 else if (strcmp(args[cur_arg], "proto") == 0) {
2462 if (!*(args[cur_arg+1])) {
2463 memprintf(errmsg, "'%s' expects a MUX protocol as argument.", args[cur_arg]);
2464 goto error;
2465 }
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002466 mux_proto = get_mux_proto(ist(args[cur_arg + 1]));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002467 if (!mux_proto) {
2468 memprintf(errmsg, "'%s' : unknown MUX protocol '%s'.", args[cur_arg], args[cur_arg+1]);
2469 goto error;
2470 }
Amaury Denoyelle90eb93f2020-11-13 12:34:58 +01002471
2472 if (strcmp(args[0], "tcp-check") == 0 && mux_proto->mode != PROTO_MODE_TCP) {
2473 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for tcp-check", args[cur_arg], args[cur_arg+1]);
2474 goto error;
2475 }
2476 else if (strcmp(args[0], "http-check") == 0 && mux_proto->mode != PROTO_MODE_HTTP) {
2477 memprintf(errmsg, "'%s' : invalid MUX protocol '%s' for http-check", args[cur_arg], args[cur_arg+1]);
2478 goto error;
2479 }
2480
Willy Tarreau51cd5952020-06-05 12:25:38 +02002481 cur_arg++;
2482 }
2483 else if (strcmp(args[cur_arg], "comment") == 0) {
2484 if (!*(args[cur_arg+1])) {
2485 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2486 goto error;
2487 }
2488 cur_arg++;
2489 free(comment);
2490 comment = strdup(args[cur_arg]);
2491 if (!comment) {
2492 memprintf(errmsg, "out of memory");
2493 goto error;
2494 }
2495 }
2496 else if (strcmp(args[cur_arg], "send-proxy") == 0)
2497 conn_opts |= TCPCHK_OPT_SEND_PROXY;
2498 else if (strcmp(args[cur_arg], "via-socks4") == 0)
2499 conn_opts |= TCPCHK_OPT_SOCKS4;
2500 else if (strcmp(args[cur_arg], "linger") == 0)
2501 conn_opts |= TCPCHK_OPT_LINGER;
2502#ifdef USE_OPENSSL
2503 else if (strcmp(args[cur_arg], "ssl") == 0) {
2504 px->options |= PR_O_TCPCHK_SSL;
2505 conn_opts |= TCPCHK_OPT_SSL;
2506 }
2507 else if (strcmp(args[cur_arg], "sni") == 0) {
2508 if (!*(args[cur_arg+1])) {
2509 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2510 goto error;
2511 }
2512 cur_arg++;
2513 free(sni);
2514 sni = strdup(args[cur_arg]);
2515 if (!sni) {
2516 memprintf(errmsg, "out of memory");
2517 goto error;
2518 }
2519 }
2520 else if (strcmp(args[cur_arg], "alpn") == 0) {
2521#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
2522 free(alpn);
2523 if (ssl_sock_parse_alpn(args[cur_arg + 1], &alpn, &alpn_len, errmsg)) {
2524 memprintf(errmsg, "'%s' : %s", args[cur_arg], *errmsg);
2525 goto error;
2526 }
2527 cur_arg++;
2528#else
2529 memprintf(errmsg, "'%s' : library does not support TLS ALPN extension.", args[cur_arg]);
2530 goto error;
2531#endif
2532 }
2533#endif /* USE_OPENSSL */
2534
2535 else {
2536 memprintf(errmsg, "expects 'comment', 'port', 'addr', 'send-proxy'"
2537#ifdef USE_OPENSSL
2538 ", 'ssl', 'sni', 'alpn'"
2539#endif /* USE_OPENSSL */
2540 " or 'via-socks4', 'linger', 'default' but got '%s' as argument.",
2541 args[cur_arg]);
2542 goto error;
2543 }
2544 cur_arg++;
2545 }
2546
2547 chk = calloc(1, sizeof(*chk));
2548 if (!chk) {
2549 memprintf(errmsg, "out of memory");
2550 goto error;
2551 }
2552 chk->action = TCPCHK_ACT_CONNECT;
2553 chk->comment = comment;
2554 chk->connect.port = port;
2555 chk->connect.options = conn_opts;
2556 chk->connect.sni = sni;
2557 chk->connect.alpn = alpn;
2558 chk->connect.alpn_len= alpn_len;
2559 chk->connect.port_expr= port_expr;
2560 chk->connect.mux_proto= mux_proto;
2561 if (sk)
2562 chk->connect.addr = *sk;
2563 return chk;
2564
2565 error:
2566 free(alpn);
2567 free(sni);
2568 free(comment);
2569 release_sample_expr(port_expr);
2570 return NULL;
2571}
2572
2573/* Parses and creates a tcp-check send rule. NULL is returned on error */
2574struct tcpcheck_rule *parse_tcpcheck_send(char **args, int cur_arg, struct proxy *px, struct list *rules,
2575 const char *file, int line, char **errmsg)
2576{
2577 struct tcpcheck_rule *chk = NULL;
2578 char *comment = NULL, *data = NULL;
2579 enum tcpcheck_send_type type = TCPCHK_SEND_UNDEF;
2580
2581 if (strcmp(args[cur_arg], "send-binary-lf") == 0)
2582 type = TCPCHK_SEND_BINARY_LF;
2583 else if (strcmp(args[cur_arg], "send-binary") == 0)
2584 type = TCPCHK_SEND_BINARY;
2585 else if (strcmp(args[cur_arg], "send-lf") == 0)
2586 type = TCPCHK_SEND_STRING_LF;
2587 else if (strcmp(args[cur_arg], "send") == 0)
2588 type = TCPCHK_SEND_STRING;
2589
2590 if (!*(args[cur_arg+1])) {
2591 memprintf(errmsg, "'%s' expects a %s as argument",
2592 (type == TCPCHK_SEND_BINARY ? "binary string": "string"), args[cur_arg]);
2593 goto error;
2594 }
2595
2596 data = args[cur_arg+1];
2597
2598 cur_arg += 2;
2599 while (*(args[cur_arg])) {
2600 if (strcmp(args[cur_arg], "comment") == 0) {
2601 if (!*(args[cur_arg+1])) {
2602 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2603 goto error;
2604 }
2605 cur_arg++;
2606 free(comment);
2607 comment = strdup(args[cur_arg]);
2608 if (!comment) {
2609 memprintf(errmsg, "out of memory");
2610 goto error;
2611 }
2612 }
2613 else {
2614 memprintf(errmsg, "expects 'comment' but got '%s' as argument.",
2615 args[cur_arg]);
2616 goto error;
2617 }
2618 cur_arg++;
2619 }
2620
2621 chk = calloc(1, sizeof(*chk));
2622 if (!chk) {
2623 memprintf(errmsg, "out of memory");
2624 goto error;
2625 }
2626 chk->action = TCPCHK_ACT_SEND;
2627 chk->comment = comment;
2628 chk->send.type = type;
2629
2630 switch (chk->send.type) {
2631 case TCPCHK_SEND_STRING:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002632 chk->send.data = ist(strdup(data));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002633 if (!isttest(chk->send.data)) {
2634 memprintf(errmsg, "out of memory");
2635 goto error;
2636 }
2637 break;
2638 case TCPCHK_SEND_BINARY: {
2639 int len = chk->send.data.len;
2640 if (parse_binary(data, &chk->send.data.ptr, &len, errmsg) == 0) {
2641 memprintf(errmsg, "'%s' invalid binary string (%s).\n", data, *errmsg);
2642 goto error;
2643 }
2644 chk->send.data.len = len;
2645 break;
2646 }
2647 case TCPCHK_SEND_STRING_LF:
2648 case TCPCHK_SEND_BINARY_LF:
2649 LIST_INIT(&chk->send.fmt);
2650 px->conf.args.ctx = ARGC_SRV;
2651 if (!parse_logformat_string(data, px, &chk->send.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2652 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", data, *errmsg);
2653 goto error;
2654 }
2655 break;
2656 case TCPCHK_SEND_HTTP:
2657 case TCPCHK_SEND_UNDEF:
2658 goto error;
2659 }
2660
2661 return chk;
2662
2663 error:
2664 free(chk);
2665 free(comment);
2666 return NULL;
2667}
2668
2669/* Parses and creates a http-check send rule. NULL is returned on error */
2670struct tcpcheck_rule *parse_tcpcheck_send_http(char **args, int cur_arg, struct proxy *px, struct list *rules,
2671 const char *file, int line, char **errmsg)
2672{
2673 struct tcpcheck_rule *chk = NULL;
2674 struct tcpcheck_http_hdr *hdr = NULL;
2675 struct http_hdr hdrs[global.tune.max_http_hdr];
2676 char *meth = NULL, *uri = NULL, *vsn = NULL;
2677 char *body = NULL, *comment = NULL;
2678 unsigned int flags = 0;
2679 int i = 0, host_hdr = -1;
2680
2681 cur_arg++;
2682 while (*(args[cur_arg])) {
2683 if (strcmp(args[cur_arg], "meth") == 0) {
2684 if (!*(args[cur_arg+1])) {
2685 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2686 goto error;
2687 }
2688 cur_arg++;
2689 meth = args[cur_arg];
2690 }
2691 else if (strcmp(args[cur_arg], "uri") == 0 || strcmp(args[cur_arg], "uri-lf") == 0) {
2692 if (!*(args[cur_arg+1])) {
2693 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2694 goto error;
2695 }
2696 flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
2697 if (strcmp(args[cur_arg], "uri-lf") == 0)
2698 flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
2699 cur_arg++;
2700 uri = args[cur_arg];
2701 }
2702 else if (strcmp(args[cur_arg], "ver") == 0) {
2703 if (!*(args[cur_arg+1])) {
2704 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2705 goto error;
2706 }
2707 cur_arg++;
2708 vsn = args[cur_arg];
2709 }
2710 else if (strcmp(args[cur_arg], "hdr") == 0) {
2711 if (!*args[cur_arg+1] || !*args[cur_arg+2]) {
2712 memprintf(errmsg, "'%s' expects <name> and <value> as arguments", args[cur_arg]);
2713 goto error;
2714 }
2715
2716 if (strcasecmp(args[cur_arg+1], "host") == 0) {
2717 if (host_hdr >= 0) {
2718 memprintf(errmsg, "'%s' header already defined (previous value is '%s')",
2719 args[cur_arg+1], istptr(hdrs[host_hdr].v));
2720 goto error;
2721 }
2722 host_hdr = i;
2723 }
Amaury Denoyelle6d975f02020-12-22 14:08:52 +01002724 else if (strcasecmp(args[cur_arg+1], "content-length") == 0 ||
Willy Tarreau51cd5952020-06-05 12:25:38 +02002725 strcasecmp(args[cur_arg+1], "transfer-encoding") == 0)
2726 goto skip_hdr;
2727
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002728 hdrs[i].n = ist(args[cur_arg + 1]);
2729 hdrs[i].v = ist(args[cur_arg + 2]);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002730 i++;
2731 skip_hdr:
2732 cur_arg += 2;
2733 }
2734 else if (strcmp(args[cur_arg], "body") == 0 || strcmp(args[cur_arg], "body-lf") == 0) {
2735 if (!*(args[cur_arg+1])) {
2736 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2737 goto error;
2738 }
2739 flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
2740 if (strcmp(args[cur_arg], "body-lf") == 0)
2741 flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
2742 cur_arg++;
2743 body = args[cur_arg];
2744 }
2745 else if (strcmp(args[cur_arg], "comment") == 0) {
2746 if (!*(args[cur_arg+1])) {
2747 memprintf(errmsg, "'%s' expects a string as argument.", args[cur_arg]);
2748 goto error;
2749 }
2750 cur_arg++;
2751 free(comment);
2752 comment = strdup(args[cur_arg]);
2753 if (!comment) {
2754 memprintf(errmsg, "out of memory");
2755 goto error;
2756 }
2757 }
2758 else {
2759 memprintf(errmsg, "expects 'comment', 'meth', 'uri', 'uri-lf', 'ver', 'hdr', 'body' or 'body-lf'"
2760 " but got '%s' as argument.", args[cur_arg]);
2761 goto error;
2762 }
2763 cur_arg++;
2764 }
2765
2766 hdrs[i].n = hdrs[i].v = IST_NULL;
2767
2768 chk = calloc(1, sizeof(*chk));
2769 if (!chk) {
2770 memprintf(errmsg, "out of memory");
2771 goto error;
2772 }
2773 chk->action = TCPCHK_ACT_SEND;
2774 chk->comment = comment; comment = NULL;
2775 chk->send.type = TCPCHK_SEND_HTTP;
2776 chk->send.http.flags = flags;
2777 LIST_INIT(&chk->send.http.hdrs);
2778
2779 if (meth) {
2780 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
2781 chk->send.http.meth.str.area = strdup(meth);
2782 chk->send.http.meth.str.data = strlen(meth);
2783 if (!chk->send.http.meth.str.area) {
2784 memprintf(errmsg, "out of memory");
2785 goto error;
2786 }
2787 }
2788 if (uri) {
2789 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) {
2790 LIST_INIT(&chk->send.http.uri_fmt);
2791 px->conf.args.ctx = ARGC_SRV;
2792 if (!parse_logformat_string(uri, px, &chk->send.http.uri_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2793 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", uri, *errmsg);
2794 goto error;
2795 }
2796 }
2797 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002798 chk->send.http.uri = ist(strdup(uri));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002799 if (!isttest(chk->send.http.uri)) {
2800 memprintf(errmsg, "out of memory");
2801 goto error;
2802 }
2803 }
2804 }
2805 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002806 chk->send.http.vsn = ist(strdup(vsn));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002807 if (!isttest(chk->send.http.vsn)) {
2808 memprintf(errmsg, "out of memory");
2809 goto error;
2810 }
2811 }
2812 for (i = 0; istlen(hdrs[i].n); i++) {
2813 hdr = calloc(1, sizeof(*hdr));
2814 if (!hdr) {
2815 memprintf(errmsg, "out of memory");
2816 goto error;
2817 }
2818 LIST_INIT(&hdr->value);
2819 hdr->name = istdup(hdrs[i].n);
2820 if (!isttest(hdr->name)) {
2821 memprintf(errmsg, "out of memory");
2822 goto error;
2823 }
2824
2825 ist0(hdrs[i].v);
2826 if (!parse_logformat_string(istptr(hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
2827 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02002828 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02002829 hdr = NULL;
2830 }
2831
2832 if (body) {
2833 if (chk->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) {
2834 LIST_INIT(&chk->send.http.body_fmt);
2835 px->conf.args.ctx = ARGC_SRV;
2836 if (!parse_logformat_string(body, px, &chk->send.http.body_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
2837 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", body, *errmsg);
2838 goto error;
2839 }
2840 }
2841 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01002842 chk->send.http.body = ist(strdup(body));
Willy Tarreau51cd5952020-06-05 12:25:38 +02002843 if (!isttest(chk->send.http.body)) {
2844 memprintf(errmsg, "out of memory");
2845 goto error;
2846 }
2847 }
2848 }
2849
2850 return chk;
2851
2852 error:
2853 free_tcpcheck_http_hdr(hdr);
2854 free_tcpcheck(chk, 0);
2855 free(comment);
2856 return NULL;
2857}
2858
2859/* Parses and creates a http-check comment rule. NULL is returned on error */
2860struct tcpcheck_rule *parse_tcpcheck_comment(char **args, int cur_arg, struct proxy *px, struct list *rules,
2861 const char *file, int line, char **errmsg)
2862{
2863 struct tcpcheck_rule *chk = NULL;
2864 char *comment = NULL;
2865
2866 if (!*(args[cur_arg+1])) {
2867 memprintf(errmsg, "expects a string as argument");
2868 goto error;
2869 }
2870 cur_arg++;
2871 comment = strdup(args[cur_arg]);
2872 if (!comment) {
2873 memprintf(errmsg, "out of memory");
2874 goto error;
2875 }
2876
2877 chk = calloc(1, sizeof(*chk));
2878 if (!chk) {
2879 memprintf(errmsg, "out of memory");
2880 goto error;
2881 }
2882 chk->action = TCPCHK_ACT_COMMENT;
2883 chk->comment = comment;
2884 return chk;
2885
2886 error:
2887 free(comment);
2888 return NULL;
2889}
2890
2891/* Parses and creates a tcp-check or an http-check expect rule. NULL is returned
2892 * on error. <proto> is set to the right protocol flags (covered by the
2893 * TCPCHK_RULES_PROTO_CHK mask).
2894 */
2895struct tcpcheck_rule *parse_tcpcheck_expect(char **args, int cur_arg, struct proxy *px,
2896 struct list *rules, unsigned int proto,
2897 const char *file, int line, char **errmsg)
2898{
2899 struct tcpcheck_rule *prev_check, *chk = NULL;
2900 struct sample_expr *status_expr = NULL;
2901 char *on_success_msg, *on_error_msg, *comment, *pattern, *npat, *vpat;
2902 enum tcpcheck_expect_type type = TCPCHK_EXPECT_UNDEF;
2903 enum healthcheck_status ok_st = HCHK_STATUS_UNKNOWN;
2904 enum healthcheck_status err_st = HCHK_STATUS_UNKNOWN;
2905 enum healthcheck_status tout_st = HCHK_STATUS_UNKNOWN;
2906 unsigned int flags = 0;
2907 long min_recv = -1;
2908 int inverse = 0;
2909
2910 on_success_msg = on_error_msg = comment = pattern = npat = vpat = NULL;
2911 if (!*(args[cur_arg+1])) {
2912 memprintf(errmsg, "expects at least a matching pattern as arguments");
2913 goto error;
2914 }
2915
2916 cur_arg++;
2917 while (*(args[cur_arg])) {
2918 int in_pattern = 0;
2919
2920 rescan:
2921 if (strcmp(args[cur_arg], "min-recv") == 0) {
2922 if (in_pattern) {
2923 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
2924 goto error;
2925 }
2926 if (!*(args[cur_arg+1])) {
2927 memprintf(errmsg, "'%s' expects a integer as argument", args[cur_arg]);
2928 goto error;
2929 }
Christopher Fauletbb9fb8b2020-11-25 17:20:57 +01002930 /* Use an signed integer here because of bufsize */
Willy Tarreau51cd5952020-06-05 12:25:38 +02002931 cur_arg++;
2932 min_recv = atol(args[cur_arg]);
2933 if (min_recv < -1 || min_recv > INT_MAX) {
2934 memprintf(errmsg, "'%s' expects -1 or an integer from 0 to INT_MAX" , args[cur_arg-1]);
2935 goto error;
2936 }
2937 }
2938 else if (*(args[cur_arg]) == '!') {
2939 in_pattern = 1;
2940 while (*(args[cur_arg]) == '!') {
2941 inverse = !inverse;
2942 args[cur_arg]++;
2943 }
2944 if (!*(args[cur_arg]))
2945 cur_arg++;
2946 goto rescan;
2947 }
2948 else if (strcmp(args[cur_arg], "string") == 0 || strcmp(args[cur_arg], "rstring") == 0) {
2949 if (type != TCPCHK_EXPECT_UNDEF) {
2950 memprintf(errmsg, "only on pattern expected");
2951 goto error;
2952 }
2953 if (proto != TCPCHK_RULES_HTTP_CHK)
2954 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING : TCPCHK_EXPECT_STRING_REGEX);
2955 else
2956 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_BODY : TCPCHK_EXPECT_HTTP_BODY_REGEX);
2957
2958 if (!*(args[cur_arg+1])) {
2959 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2960 goto error;
2961 }
2962 cur_arg++;
2963 pattern = args[cur_arg];
2964 }
2965 else if (strcmp(args[cur_arg], "binary") == 0 || strcmp(args[cur_arg], "rbinary") == 0) {
2966 if (proto == TCPCHK_RULES_HTTP_CHK)
2967 goto bad_http_kw;
2968 if (type != TCPCHK_EXPECT_UNDEF) {
2969 memprintf(errmsg, "only on pattern expected");
2970 goto error;
2971 }
2972 type = ((*(args[cur_arg]) == 'b') ? TCPCHK_EXPECT_BINARY : TCPCHK_EXPECT_BINARY_REGEX);
2973
2974 if (!*(args[cur_arg+1])) {
2975 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2976 goto error;
2977 }
2978 cur_arg++;
2979 pattern = args[cur_arg];
2980 }
2981 else if (strcmp(args[cur_arg], "string-lf") == 0 || strcmp(args[cur_arg], "binary-lf") == 0) {
2982 if (type != TCPCHK_EXPECT_UNDEF) {
2983 memprintf(errmsg, "only on pattern expected");
2984 goto error;
2985 }
2986 if (proto != TCPCHK_RULES_HTTP_CHK)
2987 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_STRING_LF : TCPCHK_EXPECT_BINARY_LF);
2988 else {
2989 if (*(args[cur_arg]) != 's')
2990 goto bad_http_kw;
2991 type = TCPCHK_EXPECT_HTTP_BODY_LF;
2992 }
2993
2994 if (!*(args[cur_arg+1])) {
2995 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
2996 goto error;
2997 }
2998 cur_arg++;
2999 pattern = args[cur_arg];
3000 }
3001 else if (strcmp(args[cur_arg], "status") == 0 || strcmp(args[cur_arg], "rstatus") == 0) {
3002 if (proto != TCPCHK_RULES_HTTP_CHK)
3003 goto bad_tcp_kw;
3004 if (type != TCPCHK_EXPECT_UNDEF) {
3005 memprintf(errmsg, "only on pattern expected");
3006 goto error;
3007 }
3008 type = ((*(args[cur_arg]) == 's') ? TCPCHK_EXPECT_HTTP_STATUS : TCPCHK_EXPECT_HTTP_STATUS_REGEX);
3009
3010 if (!*(args[cur_arg+1])) {
3011 memprintf(errmsg, "'%s' expects a <pattern> as argument", args[cur_arg]);
3012 goto error;
3013 }
3014 cur_arg++;
3015 pattern = args[cur_arg];
3016 }
3017 else if (strcmp(args[cur_arg], "custom") == 0) {
3018 if (in_pattern) {
3019 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3020 goto error;
3021 }
3022 if (type != TCPCHK_EXPECT_UNDEF) {
3023 memprintf(errmsg, "only on pattern expected");
3024 goto error;
3025 }
3026 type = TCPCHK_EXPECT_CUSTOM;
3027 }
3028 else if (strcmp(args[cur_arg], "hdr") == 0 || strcmp(args[cur_arg], "fhdr") == 0) {
3029 int orig_arg = cur_arg;
3030
3031 if (proto != TCPCHK_RULES_HTTP_CHK)
3032 goto bad_tcp_kw;
3033 if (type != TCPCHK_EXPECT_UNDEF) {
3034 memprintf(errmsg, "only on pattern expected");
3035 goto error;
3036 }
3037 type = TCPCHK_EXPECT_HTTP_HEADER;
3038
3039 if (strcmp(args[cur_arg], "fhdr") == 0)
3040 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FULL;
3041
3042 /* Parse the name pattern, mandatory */
3043 if (!*(args[cur_arg+1]) || !*(args[cur_arg+2]) ||
3044 (strcmp(args[cur_arg+1], "name") != 0 && strcmp(args[cur_arg+1], "name-lf") != 0)) {
3045 memprintf(errmsg, "'%s' expects at the name keyword as first argument followed by a pattern",
3046 args[orig_arg]);
3047 goto error;
3048 }
3049
3050 if (strcmp(args[cur_arg+1], "name-lf") == 0)
3051 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_FMT;
3052
3053 cur_arg += 2;
3054 if (strcmp(args[cur_arg], "-m") == 0) {
3055 if (!*(args[cur_arg+1])) {
3056 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3057 args[orig_arg], args[cur_arg]);
3058 goto error;
3059 }
3060 if (strcmp(args[cur_arg+1], "str") == 0)
3061 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3062 else if (strcmp(args[cur_arg+1], "beg") == 0)
3063 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_BEG;
3064 else if (strcmp(args[cur_arg+1], "end") == 0)
3065 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_END;
3066 else if (strcmp(args[cur_arg+1], "sub") == 0)
3067 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_SUB;
3068 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3069 if (flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3070 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3071 args[orig_arg]);
3072 goto error;
3073 }
3074 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_REG;
3075 }
3076 else {
3077 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3078 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3079 goto error;
3080 }
3081 cur_arg += 2;
3082 }
3083 else
3084 flags |= TCPCHK_EXPT_FL_HTTP_HNAME_STR;
3085 npat = args[cur_arg];
3086
3087 if (!*(args[cur_arg+1]) ||
3088 (strcmp(args[cur_arg+1], "value") != 0 && strcmp(args[cur_arg+1], "value-lf") != 0)) {
3089 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_NONE;
3090 goto next;
3091 }
3092 if (strcmp(args[cur_arg+1], "value-lf") == 0)
3093 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_FMT;
3094
3095 /* Parse the value pattern, optional */
3096 if (strcmp(args[cur_arg+2], "-m") == 0) {
3097 cur_arg += 2;
3098 if (!*(args[cur_arg+1])) {
3099 memprintf(errmsg, "'%s' : '%s' expects at a matching pattern ('str', 'beg', 'end', 'sub' or 'reg')",
3100 args[orig_arg], args[cur_arg]);
3101 goto error;
3102 }
3103 if (strcmp(args[cur_arg+1], "str") == 0)
3104 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3105 else if (strcmp(args[cur_arg+1], "beg") == 0)
3106 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_BEG;
3107 else if (strcmp(args[cur_arg+1], "end") == 0)
3108 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_END;
3109 else if (strcmp(args[cur_arg+1], "sub") == 0)
3110 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_SUB;
3111 else if (strcmp(args[cur_arg+1], "reg") == 0) {
3112 if (flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3113 memprintf(errmsg, "'%s': log-format string is not supported with a regex matching method",
3114 args[orig_arg]);
3115 goto error;
3116 }
3117 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_REG;
3118 }
3119 else {
3120 memprintf(errmsg, "'%s' : '%s' only supports 'str', 'beg', 'end', 'sub' or 'reg' (got '%s')",
3121 args[orig_arg], args[cur_arg], args[cur_arg+1]);
3122 goto error;
3123 }
3124 }
3125 else
3126 flags |= TCPCHK_EXPT_FL_HTTP_HVAL_STR;
3127
3128 if (!*(args[cur_arg+2])) {
3129 memprintf(errmsg, "'%s' expect a pattern with the value keyword", args[orig_arg]);
3130 goto error;
3131 }
3132 vpat = args[cur_arg+2];
3133 cur_arg += 2;
3134 }
3135 else if (strcmp(args[cur_arg], "comment") == 0) {
3136 if (in_pattern) {
3137 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3138 goto error;
3139 }
3140 if (!*(args[cur_arg+1])) {
3141 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3142 goto error;
3143 }
3144 cur_arg++;
3145 free(comment);
3146 comment = strdup(args[cur_arg]);
3147 if (!comment) {
3148 memprintf(errmsg, "out of memory");
3149 goto error;
3150 }
3151 }
3152 else if (strcmp(args[cur_arg], "on-success") == 0) {
3153 if (in_pattern) {
3154 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3155 goto error;
3156 }
3157 if (!*(args[cur_arg+1])) {
3158 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3159 goto error;
3160 }
3161 cur_arg++;
3162 on_success_msg = args[cur_arg];
3163 }
3164 else if (strcmp(args[cur_arg], "on-error") == 0) {
3165 if (in_pattern) {
3166 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3167 goto error;
3168 }
3169 if (!*(args[cur_arg+1])) {
3170 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3171 goto error;
3172 }
3173 cur_arg++;
3174 on_error_msg = args[cur_arg];
3175 }
3176 else if (strcmp(args[cur_arg], "ok-status") == 0) {
3177 if (in_pattern) {
3178 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3179 goto error;
3180 }
3181 if (!*(args[cur_arg+1])) {
3182 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3183 goto error;
3184 }
3185 if (strcasecmp(args[cur_arg+1], "L7OK") == 0)
3186 ok_st = HCHK_STATUS_L7OKD;
3187 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3188 ok_st = HCHK_STATUS_L7OKCD;
3189 else if (strcasecmp(args[cur_arg+1], "L6OK") == 0)
3190 ok_st = HCHK_STATUS_L6OK;
3191 else if (strcasecmp(args[cur_arg+1], "L4OK") == 0)
3192 ok_st = HCHK_STATUS_L4OK;
3193 else {
3194 memprintf(errmsg, "'%s' only supports 'L4OK', 'L6OK', 'L7OK' or 'L7OKC' status (got '%s').",
3195 args[cur_arg], args[cur_arg+1]);
3196 goto error;
3197 }
3198 cur_arg++;
3199 }
3200 else if (strcmp(args[cur_arg], "error-status") == 0) {
3201 if (in_pattern) {
3202 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3203 goto error;
3204 }
3205 if (!*(args[cur_arg+1])) {
3206 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3207 goto error;
3208 }
3209 if (strcasecmp(args[cur_arg+1], "L7RSP") == 0)
3210 err_st = HCHK_STATUS_L7RSP;
3211 else if (strcasecmp(args[cur_arg+1], "L7STS") == 0)
3212 err_st = HCHK_STATUS_L7STS;
Christopher Faulet83662b52020-11-20 17:47:47 +01003213 else if (strcasecmp(args[cur_arg+1], "L7OKC") == 0)
3214 err_st = HCHK_STATUS_L7OKCD;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003215 else if (strcasecmp(args[cur_arg+1], "L6RSP") == 0)
3216 err_st = HCHK_STATUS_L6RSP;
3217 else if (strcasecmp(args[cur_arg+1], "L4CON") == 0)
3218 err_st = HCHK_STATUS_L4CON;
3219 else {
3220 memprintf(errmsg, "'%s' only supports 'L4CON', 'L6RSP', 'L7RSP' or 'L7STS' status (got '%s').",
3221 args[cur_arg], args[cur_arg+1]);
3222 goto error;
3223 }
3224 cur_arg++;
3225 }
3226 else if (strcmp(args[cur_arg], "status-code") == 0) {
3227 int idx = 0;
3228
3229 if (in_pattern) {
3230 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3231 goto error;
3232 }
3233 if (!*(args[cur_arg+1])) {
3234 memprintf(errmsg, "'%s' expects an expression as argument", args[cur_arg]);
3235 goto error;
3236 }
3237
3238 cur_arg++;
3239 release_sample_expr(status_expr);
3240 px->conf.args.ctx = ARGC_SRV;
3241 status_expr = sample_parse_expr((char *[]){args[cur_arg], NULL}, &idx,
Christopher Faulet6ff7de52021-10-13 15:18:36 +02003242 file, line, errmsg, &px->conf.args, NULL);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003243 if (!status_expr) {
3244 memprintf(errmsg, "error detected while parsing status-code expression : %s", *errmsg);
3245 goto error;
3246 }
3247 if (!(status_expr->fetch->val & SMP_VAL_BE_CHK_RUL)) {
3248 memprintf(errmsg, "error detected while parsing status-code expression : "
3249 " fetch method '%s' extracts information from '%s', "
3250 "none of which is available here.\n",
3251 args[cur_arg], sample_src_names(status_expr->fetch->use));
3252 goto error;
3253 }
3254 px->http_needed |= !!(status_expr->fetch->use & SMP_USE_HTTP_ANY);
3255 }
3256 else if (strcmp(args[cur_arg], "tout-status") == 0) {
3257 if (in_pattern) {
3258 memprintf(errmsg, "[!] not supported with '%s'", args[cur_arg]);
3259 goto error;
3260 }
3261 if (!*(args[cur_arg+1])) {
3262 memprintf(errmsg, "'%s' expects a string as argument", args[cur_arg]);
3263 goto error;
3264 }
3265 if (strcasecmp(args[cur_arg+1], "L7TOUT") == 0)
3266 tout_st = HCHK_STATUS_L7TOUT;
3267 else if (strcasecmp(args[cur_arg+1], "L6TOUT") == 0)
3268 tout_st = HCHK_STATUS_L6TOUT;
3269 else if (strcasecmp(args[cur_arg+1], "L4TOUT") == 0)
3270 tout_st = HCHK_STATUS_L4TOUT;
3271 else {
3272 memprintf(errmsg, "'%s' only supports 'L4TOUT', 'L6TOUT' or 'L7TOUT' status (got '%s').",
3273 args[cur_arg], args[cur_arg+1]);
3274 goto error;
3275 }
3276 cur_arg++;
3277 }
3278 else {
3279 if (proto == TCPCHK_RULES_HTTP_CHK) {
3280 bad_http_kw:
3281 memprintf(errmsg, "'only supports min-recv, [!]string', '[!]rstring', '[!]string-lf', '[!]status', "
3282 "'[!]rstatus', [!]hdr, [!]fhdr or comment but got '%s' as argument.", args[cur_arg]);
3283 }
3284 else {
3285 bad_tcp_kw:
3286 memprintf(errmsg, "'only supports min-recv, '[!]binary', '[!]string', '[!]rstring', '[!]string-lf'"
3287 "'[!]rbinary', '[!]binary-lf' or comment but got '%s' as argument.", args[cur_arg]);
3288 }
3289 goto error;
3290 }
3291 next:
3292 cur_arg++;
3293 }
3294
3295 chk = calloc(1, sizeof(*chk));
3296 if (!chk) {
3297 memprintf(errmsg, "out of memory");
3298 goto error;
3299 }
3300 chk->action = TCPCHK_ACT_EXPECT;
3301 LIST_INIT(&chk->expect.onerror_fmt);
3302 LIST_INIT(&chk->expect.onsuccess_fmt);
3303 chk->comment = comment; comment = NULL;
3304 chk->expect.type = type;
3305 chk->expect.min_recv = min_recv;
3306 chk->expect.flags = flags | (inverse ? TCPCHK_EXPT_FL_INV : 0);
3307 chk->expect.ok_status = ok_st;
3308 chk->expect.err_status = err_st;
3309 chk->expect.tout_status = tout_st;
3310 chk->expect.status_expr = status_expr; status_expr = NULL;
3311
3312 if (on_success_msg) {
3313 px->conf.args.ctx = ARGC_SRV;
3314 if (!parse_logformat_string(on_success_msg, px, &chk->expect.onsuccess_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3315 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_success_msg, *errmsg);
3316 goto error;
3317 }
3318 }
3319 if (on_error_msg) {
3320 px->conf.args.ctx = ARGC_SRV;
3321 if (!parse_logformat_string(on_error_msg, px, &chk->expect.onerror_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3322 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", on_error_msg, *errmsg);
3323 goto error;
3324 }
3325 }
3326
3327 switch (chk->expect.type) {
3328 case TCPCHK_EXPECT_HTTP_STATUS: {
3329 const char *p = pattern;
3330 unsigned int c1,c2;
3331
3332 chk->expect.codes.codes = NULL;
3333 chk->expect.codes.num = 0;
3334 while (1) {
3335 c1 = c2 = read_uint(&p, pattern + strlen(pattern));
3336 if (*p == '-') {
3337 p++;
3338 c2 = read_uint(&p, pattern + strlen(pattern));
3339 }
3340 if (c1 > c2) {
3341 memprintf(errmsg, "invalid range of status codes '%s'", pattern);
3342 goto error;
3343 }
3344
3345 chk->expect.codes.num++;
3346 chk->expect.codes.codes = my_realloc2(chk->expect.codes.codes,
3347 chk->expect.codes.num * sizeof(*chk->expect.codes.codes));
3348 if (!chk->expect.codes.codes) {
3349 memprintf(errmsg, "out of memory");
3350 goto error;
3351 }
3352 chk->expect.codes.codes[chk->expect.codes.num-1][0] = c1;
3353 chk->expect.codes.codes[chk->expect.codes.num-1][1] = c2;
3354
3355 if (*p == '\0')
3356 break;
3357 if (*p != ',') {
3358 memprintf(errmsg, "invalid character '%c' in the list of status codes", *p);
3359 goto error;
3360 }
3361 p++;
3362 }
3363 break;
3364 }
3365 case TCPCHK_EXPECT_STRING:
3366 case TCPCHK_EXPECT_HTTP_BODY:
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003367 chk->expect.data = ist(strdup(pattern));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003368 if (!isttest(chk->expect.data)) {
3369 memprintf(errmsg, "out of memory");
3370 goto error;
3371 }
3372 break;
3373 case TCPCHK_EXPECT_BINARY: {
3374 int len = chk->expect.data.len;
3375
3376 if (parse_binary(pattern, &chk->expect.data.ptr, &len, errmsg) == 0) {
3377 memprintf(errmsg, "invalid binary string (%s)", *errmsg);
3378 goto error;
3379 }
3380 chk->expect.data.len = len;
3381 break;
3382 }
3383 case TCPCHK_EXPECT_STRING_REGEX:
3384 case TCPCHK_EXPECT_BINARY_REGEX:
3385 case TCPCHK_EXPECT_HTTP_STATUS_REGEX:
3386 case TCPCHK_EXPECT_HTTP_BODY_REGEX:
3387 chk->expect.regex = regex_comp(pattern, 1, 0, errmsg);
3388 if (!chk->expect.regex)
3389 goto error;
3390 break;
3391
3392 case TCPCHK_EXPECT_STRING_LF:
3393 case TCPCHK_EXPECT_BINARY_LF:
3394 case TCPCHK_EXPECT_HTTP_BODY_LF:
3395 LIST_INIT(&chk->expect.fmt);
3396 px->conf.args.ctx = ARGC_SRV;
3397 if (!parse_logformat_string(pattern, px, &chk->expect.fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3398 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", pattern, *errmsg);
3399 goto error;
3400 }
3401 break;
3402
3403 case TCPCHK_EXPECT_HTTP_HEADER:
3404 if (!npat) {
3405 memprintf(errmsg, "unexpected error, undefined header name pattern");
3406 goto error;
3407 }
3408 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_REG) {
3409 chk->expect.hdr.name_re = regex_comp(npat, 0, 0, errmsg);
3410 if (!chk->expect.hdr.name_re)
3411 goto error;
3412 }
3413 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HNAME_FMT) {
3414 px->conf.args.ctx = ARGC_SRV;
3415 LIST_INIT(&chk->expect.hdr.name_fmt);
3416 if (!parse_logformat_string(npat, px, &chk->expect.hdr.name_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3417 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3418 goto error;
3419 }
3420 }
3421 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003422 chk->expect.hdr.name = ist(strdup(npat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003423 if (!isttest(chk->expect.hdr.name)) {
3424 memprintf(errmsg, "out of memory");
3425 goto error;
3426 }
3427 }
3428
3429 if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_NONE) {
3430 chk->expect.hdr.value = IST_NULL;
3431 break;
3432 }
3433
3434 if (!vpat) {
3435 memprintf(errmsg, "unexpected error, undefined header value pattern");
3436 goto error;
3437 }
3438 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_REG) {
3439 chk->expect.hdr.value_re = regex_comp(vpat, 1, 0, errmsg);
3440 if (!chk->expect.hdr.value_re)
3441 goto error;
3442 }
3443 else if (chk->expect.flags & TCPCHK_EXPT_FL_HTTP_HVAL_FMT) {
3444 px->conf.args.ctx = ARGC_SRV;
3445 LIST_INIT(&chk->expect.hdr.value_fmt);
3446 if (!parse_logformat_string(vpat, px, &chk->expect.hdr.value_fmt, 0, SMP_VAL_BE_CHK_RUL, errmsg)) {
3447 memprintf(errmsg, "'%s' invalid log-format string (%s).\n", npat, *errmsg);
3448 goto error;
3449 }
3450 }
3451 else {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003452 chk->expect.hdr.value = ist(strdup(vpat));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003453 if (!isttest(chk->expect.hdr.value)) {
3454 memprintf(errmsg, "out of memory");
3455 goto error;
3456 }
3457 }
3458
3459 break;
3460 case TCPCHK_EXPECT_CUSTOM:
3461 chk->expect.custom = NULL; /* Must be defined by the caller ! */
3462 break;
3463 case TCPCHK_EXPECT_UNDEF:
3464 memprintf(errmsg, "pattern not found");
3465 goto error;
3466 }
3467
3468 /* All tcp-check expect points back to the first inverse expect rule in
3469 * a chain of one or more expect rule, potentially itself.
3470 */
3471 chk->expect.head = chk;
3472 list_for_each_entry_rev(prev_check, rules, list) {
3473 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3474 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3475 chk->expect.head = prev_check;
3476 continue;
3477 }
3478 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3479 break;
3480 }
3481 return chk;
3482
3483 error:
3484 free_tcpcheck(chk, 0);
3485 free(comment);
3486 release_sample_expr(status_expr);
3487 return NULL;
3488}
3489
3490/* Overwrites fields of the old http send rule with those of the new one. When
3491 * replaced, old values are freed and replaced by the new ones. New values are
3492 * not copied but transferred. At the end <new> should be empty and can be
3493 * safely released. This function never fails.
3494 */
3495void tcpcheck_overwrite_send_http_rule(struct tcpcheck_rule *old, struct tcpcheck_rule *new)
3496{
3497 struct logformat_node *lf, *lfb;
3498 struct tcpcheck_http_hdr *hdr, *bhdr;
3499
3500
3501 if (new->send.http.meth.str.area) {
3502 free(old->send.http.meth.str.area);
3503 old->send.http.meth.meth = new->send.http.meth.meth;
3504 old->send.http.meth.str.area = new->send.http.meth.str.area;
3505 old->send.http.meth.str.data = new->send.http.meth.str.data;
3506 new->send.http.meth.str = BUF_NULL;
3507 }
3508
3509 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && isttest(new->send.http.uri)) {
3510 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3511 istfree(&old->send.http.uri);
3512 else
3513 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3514 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_URI_FMT;
3515 old->send.http.uri = new->send.http.uri;
3516 new->send.http.uri = IST_NULL;
3517 }
3518 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT) && !LIST_ISEMPTY(&new->send.http.uri_fmt)) {
3519 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_URI_FMT))
3520 istfree(&old->send.http.uri);
3521 else
3522 free_tcpcheck_fmt(&old->send.http.uri_fmt);
3523 old->send.http.flags |= TCPCHK_SND_HTTP_FL_URI_FMT;
3524 LIST_INIT(&old->send.http.uri_fmt);
3525 list_for_each_entry_safe(lf, lfb, &new->send.http.uri_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003526 LIST_DELETE(&lf->list);
3527 LIST_APPEND(&old->send.http.uri_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003528 }
3529 }
3530
3531 if (isttest(new->send.http.vsn)) {
3532 istfree(&old->send.http.vsn);
3533 old->send.http.vsn = new->send.http.vsn;
3534 new->send.http.vsn = IST_NULL;
3535 }
3536
3537 free_tcpcheck_http_hdrs(&old->send.http.hdrs);
3538 list_for_each_entry_safe(hdr, bhdr, &new->send.http.hdrs, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003539 LIST_DELETE(&hdr->list);
3540 LIST_APPEND(&old->send.http.hdrs, &hdr->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003541 }
3542
3543 if (!(new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && isttest(new->send.http.body)) {
3544 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3545 istfree(&old->send.http.body);
3546 else
3547 free_tcpcheck_fmt(&old->send.http.body_fmt);
3548 old->send.http.flags &= ~TCPCHK_SND_HTTP_FL_BODY_FMT;
3549 old->send.http.body = new->send.http.body;
3550 new->send.http.body = IST_NULL;
3551 }
3552 else if ((new->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT) && !LIST_ISEMPTY(&new->send.http.body_fmt)) {
3553 if (!(old->send.http.flags & TCPCHK_SND_HTTP_FL_BODY_FMT))
3554 istfree(&old->send.http.body);
3555 else
3556 free_tcpcheck_fmt(&old->send.http.body_fmt);
3557 old->send.http.flags |= TCPCHK_SND_HTTP_FL_BODY_FMT;
3558 LIST_INIT(&old->send.http.body_fmt);
3559 list_for_each_entry_safe(lf, lfb, &new->send.http.body_fmt, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003560 LIST_DELETE(&lf->list);
3561 LIST_APPEND(&old->send.http.body_fmt, &lf->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003562 }
3563 }
3564}
3565
3566/* Internal function used to add an http-check rule in a list during the config
3567 * parsing step. Depending on its type, and the previously inserted rules, a
3568 * specific action may be performed or an error may be reported. This functions
3569 * returns 1 on success and 0 on error and <errmsg> is filled with the error
3570 * message.
3571 */
3572int tcpcheck_add_http_rule(struct tcpcheck_rule *chk, struct tcpcheck_rules *rules, char **errmsg)
3573{
3574 struct tcpcheck_rule *r;
3575
3576 /* the implicit send rule coming from an "option httpchk" line must be
3577 * merged with the first explici http-check send rule, if
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003578 * any. Depending on the declaration order some tests are required.
Willy Tarreau51cd5952020-06-05 12:25:38 +02003579 *
Ilya Shipitsin47d17182020-06-21 21:42:57 +05003580 * Some tests are also required for other kinds of http-check rules to be
Willy Tarreau51cd5952020-06-05 12:25:38 +02003581 * sure the ruleset remains valid.
3582 */
3583
3584 if (chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3585 /* Tries to add an implicit http-check send rule from an "option httpchk" line.
3586 * First, the first rule is retrieved, skipping the first CONNECT, if any, and
3587 * following tests are performed :
3588 *
3589 * 1- If there is no such rule or if it is not a send rule, the implicit send
3590 * rule is pushed in front of the ruleset
3591 *
3592 * 2- If it is another implicit send rule, it is replaced with the new one.
3593 *
3594 * 3- Otherwise, it means it is an explicit send rule. In this case we merge
3595 * both, overwriting the old send rule (the explicit one) with info of the
3596 * new send rule (the implicit one).
3597 */
3598 r = get_first_tcpcheck_rule(rules);
3599 if (r && r->action == TCPCHK_ACT_CONNECT)
3600 r = get_next_tcpcheck_rule(rules, r);
3601 if (!r || r->action != TCPCHK_ACT_SEND)
Willy Tarreau2b718102021-04-21 07:32:39 +02003602 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003603 else if (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003604 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003605 free_tcpcheck(r, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003606 LIST_INSERT(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003607 }
3608 else {
3609 tcpcheck_overwrite_send_http_rule(r, chk);
3610 free_tcpcheck(chk, 0);
3611 }
3612 }
3613 else {
3614 /* Tries to add an explicit http-check rule. First of all we check the typefo the
3615 * last inserted rule to be sure it is valid. Then for send rule, we try to merge it
3616 * with an existing implicit send rule, if any. At the end, if there is no error,
3617 * the rule is appended to the list.
3618 */
3619
3620 r = get_last_tcpcheck_rule(rules);
3621 if (!r || (r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)))
3622 /* no error */;
3623 else if (r->action != TCPCHK_ACT_CONNECT && chk->action == TCPCHK_ACT_SEND) {
3624 memprintf(errmsg, "unable to add http-check send rule at step %d (missing connect rule).",
3625 chk->index+1);
3626 return 0;
3627 }
3628 else if (r->action != TCPCHK_ACT_SEND && r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_EXPECT) {
3629 memprintf(errmsg, "unable to add http-check expect rule at step %d (missing send rule).",
3630 chk->index+1);
3631 return 0;
3632 }
3633 else if (r->action != TCPCHK_ACT_EXPECT && chk->action == TCPCHK_ACT_CONNECT) {
3634 memprintf(errmsg, "unable to add http-check connect rule at step %d (missing expect rule).",
3635 chk->index+1);
3636 return 0;
3637 }
3638
3639 if (chk->action == TCPCHK_ACT_SEND) {
3640 r = get_first_tcpcheck_rule(rules);
3641 if (r && r->action == TCPCHK_ACT_SEND && (r->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3642 tcpcheck_overwrite_send_http_rule(r, chk);
3643 free_tcpcheck(chk, 0);
Willy Tarreau2b718102021-04-21 07:32:39 +02003644 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003645 r->send.http.flags &= ~TCPCHK_SND_HTTP_FROM_OPT;
3646 chk = r;
3647 }
3648 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003649 LIST_APPEND(rules->list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003650 }
3651 return 1;
3652}
3653
3654/* Check tcp-check health-check configuration for the proxy <px>. */
3655static int check_proxy_tcpcheck(struct proxy *px)
3656{
3657 struct tcpcheck_rule *chk, *back;
3658 char *comment = NULL, *errmsg = NULL;
3659 enum tcpcheck_rule_type prev_action = TCPCHK_ACT_COMMENT;
Christopher Fauletfc633b62020-11-06 15:24:23 +01003660 int ret = ERR_NONE;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003661
3662 if (!(px->cap & PR_CAP_BE) || (px->options2 & PR_O2_CHK_ANY) != PR_O2_TCPCHK_CHK) {
3663 deinit_proxy_tcpcheck(px);
3664 goto out;
3665 }
3666
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003667 ha_free(&px->check_command);
3668 ha_free(&px->check_path);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003669
3670 if (!px->tcpcheck_rules.list) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003671 ha_alert("proxy '%s' : tcp-check configured but no ruleset defined.\n", px->id);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003672 ret |= ERR_ALERT | ERR_FATAL;
3673 goto out;
3674 }
3675
3676 /* HTTP ruleset only : */
3677 if ((px->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
3678 struct tcpcheck_rule *next;
3679
3680 /* move remaining implicit send rule from "option httpchk" line to the right place.
3681 * If such rule exists, it must be the first one. In this case, the rule is moved
3682 * after the first connect rule, if any. Otherwise, nothing is done.
3683 */
3684 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3685 if (chk && chk->action == TCPCHK_ACT_SEND && (chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT)) {
3686 next = get_next_tcpcheck_rule(&px->tcpcheck_rules, chk);
3687 if (next && next->action == TCPCHK_ACT_CONNECT) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003688 LIST_DELETE(&chk->list);
3689 LIST_INSERT(&next->list, &chk->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003690 chk->index = next->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003691 }
3692 }
3693
3694 /* add implicit expect rule if the last one is a send. It is inherited from previous
3695 * versions where the http expect rule was optional. Now it is possible to chained
3696 * send/expect rules but the last expect may still be implicit.
3697 */
3698 chk = get_last_tcpcheck_rule(&px->tcpcheck_rules);
3699 if (chk && chk->action == TCPCHK_ACT_SEND) {
3700 next = parse_tcpcheck_expect((char *[]){"http-check", "expect", "status", "200-399", ""},
3701 1, px, px->tcpcheck_rules.list, TCPCHK_RULES_HTTP_CHK,
3702 px->conf.file, px->conf.line, &errmsg);
3703 if (!next) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003704 ha_alert("proxy '%s': unable to add implicit http-check expect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003705 "(%s).\n", px->id, errmsg);
3706 free(errmsg);
3707 ret |= ERR_ALERT | ERR_FATAL;
3708 goto out;
3709 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003710 LIST_APPEND(px->tcpcheck_rules.list, &next->list);
Christopher Fauletfa5880b2021-06-25 11:37:45 +02003711 next->index = chk->index + 1;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003712 }
3713 }
3714
3715 /* For all ruleset: */
3716
3717 /* If there is no connect rule preceding all send / expect rules, an
3718 * implicit one is inserted before all others.
3719 */
3720 chk = get_first_tcpcheck_rule(&px->tcpcheck_rules);
3721 if (!chk || chk->action != TCPCHK_ACT_CONNECT) {
3722 chk = calloc(1, sizeof(*chk));
3723 if (!chk) {
Amaury Denoyelle11124302021-06-04 18:22:08 +02003724 ha_alert("proxy '%s': unable to add implicit tcp-check connect rule "
Willy Tarreau51cd5952020-06-05 12:25:38 +02003725 "(out of memory).\n", px->id);
3726 ret |= ERR_ALERT | ERR_FATAL;
3727 goto out;
3728 }
3729 chk->action = TCPCHK_ACT_CONNECT;
3730 chk->connect.options = (TCPCHK_OPT_DEFAULT_CONNECT|TCPCHK_OPT_IMPLICIT);
Willy Tarreau2b718102021-04-21 07:32:39 +02003731 LIST_INSERT(px->tcpcheck_rules.list, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003732 }
3733
3734 /* Remove all comment rules. To do so, when a such rule is found, the
3735 * comment is assigned to the following rule(s).
3736 */
3737 list_for_each_entry_safe(chk, back, px->tcpcheck_rules.list, list) {
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003738 if (chk->action != prev_action && prev_action != TCPCHK_ACT_COMMENT)
3739 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003740
3741 prev_action = chk->action;
3742 switch (chk->action) {
3743 case TCPCHK_ACT_COMMENT:
3744 free(comment);
3745 comment = chk->comment;
Willy Tarreau2b718102021-04-21 07:32:39 +02003746 LIST_DELETE(&chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003747 free(chk);
3748 break;
3749 case TCPCHK_ACT_CONNECT:
3750 if (!chk->comment && comment)
3751 chk->comment = strdup(comment);
Tim Duesterhus588b3142020-05-29 14:35:51 +02003752 /* fall through */
Willy Tarreau51cd5952020-06-05 12:25:38 +02003753 case TCPCHK_ACT_ACTION_KW:
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003754 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003755 break;
3756 case TCPCHK_ACT_SEND:
3757 case TCPCHK_ACT_EXPECT:
3758 if (!chk->comment && comment)
3759 chk->comment = strdup(comment);
3760 break;
3761 }
3762 }
Willy Tarreau61cfdf42021-02-20 10:46:51 +01003763 ha_free(&comment);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003764
3765 out:
3766 return ret;
3767}
3768
3769void deinit_proxy_tcpcheck(struct proxy *px)
3770{
3771 free_tcpcheck_vars(&px->tcpcheck_rules.preset_vars);
3772 px->tcpcheck_rules.flags = 0;
3773 px->tcpcheck_rules.list = NULL;
3774}
3775
3776static void deinit_tcpchecks()
3777{
3778 struct tcpcheck_ruleset *rs;
3779 struct tcpcheck_rule *r, *rb;
3780 struct ebpt_node *node, *next;
3781
3782 node = ebpt_first(&shared_tcpchecks);
3783 while (node) {
3784 next = ebpt_next(node);
3785 ebpt_delete(node);
3786 free(node->key);
3787 rs = container_of(node, typeof(*rs), node);
3788 list_for_each_entry_safe(r, rb, &rs->rules, list) {
Willy Tarreau2b718102021-04-21 07:32:39 +02003789 LIST_DELETE(&r->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003790 free_tcpcheck(r, 0);
3791 }
3792 free(rs);
3793 node = next;
3794 }
3795}
3796
3797int add_tcpcheck_expect_str(struct tcpcheck_rules *rules, const char *str)
3798{
3799 struct tcpcheck_rule *tcpcheck, *prev_check;
3800 struct tcpcheck_expect *expect;
3801
Willy Tarreau6922e552021-03-22 21:11:45 +01003802 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003803 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003804 tcpcheck->action = TCPCHK_ACT_EXPECT;
3805
3806 expect = &tcpcheck->expect;
3807 expect->type = TCPCHK_EXPECT_STRING;
3808 LIST_INIT(&expect->onerror_fmt);
3809 LIST_INIT(&expect->onsuccess_fmt);
3810 expect->ok_status = HCHK_STATUS_L7OKD;
3811 expect->err_status = HCHK_STATUS_L7RSP;
3812 expect->tout_status = HCHK_STATUS_L7TOUT;
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01003813 expect->data = ist(strdup(str));
Willy Tarreau51cd5952020-06-05 12:25:38 +02003814 if (!isttest(expect->data)) {
3815 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3816 return 0;
3817 }
3818
3819 /* All tcp-check expect points back to the first inverse expect rule
3820 * in a chain of one or more expect rule, potentially itself.
3821 */
3822 tcpcheck->expect.head = tcpcheck;
3823 list_for_each_entry_rev(prev_check, rules->list, list) {
3824 if (prev_check->action == TCPCHK_ACT_EXPECT) {
3825 if (prev_check->expect.flags & TCPCHK_EXPT_FL_INV)
3826 tcpcheck->expect.head = prev_check;
3827 continue;
3828 }
3829 if (prev_check->action != TCPCHK_ACT_COMMENT && prev_check->action != TCPCHK_ACT_ACTION_KW)
3830 break;
3831 }
Willy Tarreau2b718102021-04-21 07:32:39 +02003832 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003833 return 1;
3834}
3835
3836int add_tcpcheck_send_strs(struct tcpcheck_rules *rules, const char * const *strs)
3837{
3838 struct tcpcheck_rule *tcpcheck;
3839 struct tcpcheck_send *send;
3840 const char *in;
3841 char *dst;
3842 int i;
3843
Willy Tarreau6922e552021-03-22 21:11:45 +01003844 if ((tcpcheck = pool_zalloc(pool_head_tcpcheck_rule)) == NULL)
Willy Tarreau51cd5952020-06-05 12:25:38 +02003845 return 0;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003846 tcpcheck->action = TCPCHK_ACT_SEND;
3847
3848 send = &tcpcheck->send;
3849 send->type = TCPCHK_SEND_STRING;
3850
3851 for (i = 0; strs[i]; i++)
3852 send->data.len += strlen(strs[i]);
3853
3854 send->data.ptr = malloc(istlen(send->data) + 1);
3855 if (!isttest(send->data)) {
3856 pool_free(pool_head_tcpcheck_rule, tcpcheck);
3857 return 0;
3858 }
3859
3860 dst = istptr(send->data);
3861 for (i = 0; strs[i]; i++)
3862 for (in = strs[i]; (*dst = *in++); dst++);
3863 *dst = 0;
3864
Willy Tarreau2b718102021-04-21 07:32:39 +02003865 LIST_APPEND(rules->list, &tcpcheck->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003866 return 1;
3867}
3868
3869/* Parses the "tcp-check" proxy keyword */
3870static int proxy_parse_tcpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003871 const struct proxy *defpx, const char *file, int line,
Willy Tarreau51cd5952020-06-05 12:25:38 +02003872 char **errmsg)
3873{
3874 struct tcpcheck_ruleset *rs = NULL;
3875 struct tcpcheck_rule *chk = NULL;
3876 int index, cur_arg, ret = 0;
3877
3878 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3879 ret = 1;
3880
3881 /* Deduce the ruleset name from the proxy info */
3882 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
3883 ((curpx == defpx) ? "defaults" : curpx->id),
3884 curpx->conf.file, curpx->conf.line);
3885
3886 rs = find_tcpcheck_ruleset(b_orig(&trash));
3887 if (rs == NULL) {
3888 rs = create_tcpcheck_ruleset(b_orig(&trash));
3889 if (rs == NULL) {
3890 memprintf(errmsg, "out of memory.\n");
3891 goto error;
3892 }
3893 }
3894
3895 index = 0;
3896 if (!LIST_ISEMPTY(&rs->rules)) {
3897 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3898 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01003899 chk = NULL;
Willy Tarreau51cd5952020-06-05 12:25:38 +02003900 }
3901
3902 cur_arg = 1;
3903 if (strcmp(args[cur_arg], "connect") == 0)
3904 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3905 else if (strcmp(args[cur_arg], "send") == 0 || strcmp(args[cur_arg], "send-binary") == 0 ||
3906 strcmp(args[cur_arg], "send-lf") == 0 || strcmp(args[cur_arg], "send-binary-lf") == 0)
3907 chk = parse_tcpcheck_send(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3908 else if (strcmp(args[cur_arg], "expect") == 0)
3909 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, 0, file, line, errmsg);
3910 else if (strcmp(args[cur_arg], "comment") == 0)
3911 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
3912 else {
3913 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
3914
3915 if (!kw) {
3916 action_kw_tcp_check_build_list(&trash);
3917 memprintf(errmsg, "'%s' only supports 'comment', 'connect', 'send', 'send-binary', 'expect'"
3918 "%s%s. but got '%s'",
3919 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
3920 goto error;
3921 }
3922 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
3923 }
3924
3925 if (!chk) {
3926 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
3927 goto error;
3928 }
3929 ret = (ret || (*errmsg != NULL)); /* Handle warning */
3930
3931 /* No error: add the tcp-check rule in the list */
3932 chk->index = index;
Willy Tarreau2b718102021-04-21 07:32:39 +02003933 LIST_APPEND(&rs->rules, &chk->list);
Willy Tarreau51cd5952020-06-05 12:25:38 +02003934
3935 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
3936 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
3937 /* Use this ruleset if the proxy already has tcp-check enabled */
3938 curpx->tcpcheck_rules.list = &rs->rules;
3939 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_TCP_RS;
3940 }
3941 else {
3942 /* mark this ruleset as unused for now */
3943 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_TCP_RS;
3944 }
3945
3946 return ret;
3947
3948 error:
3949 free_tcpcheck(chk, 0);
3950 free_tcpcheck_ruleset(rs);
3951 return -1;
3952}
3953
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003954/* Parses the "http-check" proxy keyword */
3955static int proxy_parse_httpcheck(char **args, int section, struct proxy *curpx,
Willy Tarreau01825162021-03-09 09:53:46 +01003956 const struct proxy *defpx, const char *file, int line,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01003957 char **errmsg)
3958{
3959 struct tcpcheck_ruleset *rs = NULL;
3960 struct tcpcheck_rule *chk = NULL;
3961 int index, cur_arg, ret = 0;
3962
3963 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[0], NULL))
3964 ret = 1;
3965
3966 cur_arg = 1;
3967 if (strcmp(args[cur_arg], "disable-on-404") == 0) {
3968 /* enable a graceful server shutdown on an HTTP 404 response */
3969 curpx->options |= PR_O_DISABLE404;
3970 if (too_many_args(1, args, errmsg, NULL))
3971 goto error;
3972 goto out;
3973 }
3974 else if (strcmp(args[cur_arg], "send-state") == 0) {
3975 /* enable emission of the apparent state of a server in HTTP checks */
3976 curpx->options2 |= PR_O2_CHK_SNDST;
3977 if (too_many_args(1, args, errmsg, NULL))
3978 goto error;
3979 goto out;
3980 }
3981
3982 /* Deduce the ruleset name from the proxy info */
3983 chunk_printf(&trash, "*http-check-%s_%s-%d",
3984 ((curpx == defpx) ? "defaults" : curpx->id),
3985 curpx->conf.file, curpx->conf.line);
3986
3987 rs = find_tcpcheck_ruleset(b_orig(&trash));
3988 if (rs == NULL) {
3989 rs = create_tcpcheck_ruleset(b_orig(&trash));
3990 if (rs == NULL) {
3991 memprintf(errmsg, "out of memory.\n");
3992 goto error;
3993 }
3994 }
3995
3996 index = 0;
3997 if (!LIST_ISEMPTY(&rs->rules)) {
3998 chk = LIST_PREV(&rs->rules, typeof(chk), list);
3999 if (chk->action != TCPCHK_ACT_SEND || !(chk->send.http.flags & TCPCHK_SND_HTTP_FROM_OPT))
4000 index = chk->index + 1;
Christopher Fauletcd03be72021-03-12 12:00:14 +01004001 chk = NULL;
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004002 }
4003
4004 if (strcmp(args[cur_arg], "connect") == 0)
4005 chk = parse_tcpcheck_connect(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4006 else if (strcmp(args[cur_arg], "send") == 0)
4007 chk = parse_tcpcheck_send_http(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4008 else if (strcmp(args[cur_arg], "expect") == 0)
4009 chk = parse_tcpcheck_expect(args, cur_arg, curpx, &rs->rules, TCPCHK_RULES_HTTP_CHK,
4010 file, line, errmsg);
4011 else if (strcmp(args[cur_arg], "comment") == 0)
4012 chk = parse_tcpcheck_comment(args, cur_arg, curpx, &rs->rules, file, line, errmsg);
4013 else {
4014 struct action_kw *kw = action_kw_tcp_check_lookup(args[cur_arg]);
4015
4016 if (!kw) {
4017 action_kw_tcp_check_build_list(&trash);
4018 memprintf(errmsg, "'%s' only supports 'disable-on-404', 'send-state', 'comment', 'connect',"
4019 " 'send', 'expect'%s%s. but got '%s'",
4020 args[0], (*trash.area ? ", " : ""), trash.area, args[1]);
4021 goto error;
4022 }
4023 chk = parse_tcpcheck_action(args, cur_arg, curpx, &rs->rules, kw, file, line, errmsg);
4024 }
4025
4026 if (!chk) {
4027 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4028 goto error;
4029 }
4030 ret = (*errmsg != NULL); /* Handle warning */
4031
4032 chk->index = index;
4033 if ((curpx->options2 & PR_O2_CHK_ANY) == PR_O2_TCPCHK_CHK &&
4034 (curpx->tcpcheck_rules.flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK) {
4035 /* Use this ruleset if the proxy already has http-check enabled */
4036 curpx->tcpcheck_rules.list = &rs->rules;
4037 curpx->tcpcheck_rules.flags &= ~TCPCHK_RULES_UNUSED_HTTP_RS;
4038 if (!tcpcheck_add_http_rule(chk, &curpx->tcpcheck_rules, errmsg)) {
4039 memprintf(errmsg, "'%s %s' : %s.", args[0], args[1], *errmsg);
4040 curpx->tcpcheck_rules.list = NULL;
4041 goto error;
4042 }
4043 }
4044 else {
4045 /* mark this ruleset as unused for now */
4046 curpx->tcpcheck_rules.flags |= TCPCHK_RULES_UNUSED_HTTP_RS;
Willy Tarreau2b718102021-04-21 07:32:39 +02004047 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004048 }
4049
4050 out:
4051 return ret;
4052
4053 error:
4054 free_tcpcheck(chk, 0);
4055 free_tcpcheck_ruleset(rs);
4056 return -1;
4057}
4058
4059/* Parses the "option redis-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004060int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004061 const char *file, int line)
4062{
4063 static char *redis_req = "*1\r\n$4\r\nPING\r\n";
4064 static char *redis_res = "+PONG\r\n";
4065
4066 struct tcpcheck_ruleset *rs = NULL;
4067 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4068 struct tcpcheck_rule *chk;
4069 char *errmsg = NULL;
4070 int err_code = 0;
4071
4072 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4073 err_code |= ERR_WARN;
4074
4075 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4076 goto out;
4077
4078 curpx->options2 &= ~PR_O2_CHK_ANY;
4079 curpx->options2 |= PR_O2_TCPCHK_CHK;
4080
4081 free_tcpcheck_vars(&rules->preset_vars);
4082 rules->list = NULL;
4083 rules->flags = 0;
4084
4085 rs = find_tcpcheck_ruleset("*redis-check");
4086 if (rs)
4087 goto ruleset_found;
4088
4089 rs = create_tcpcheck_ruleset("*redis-check");
4090 if (rs == NULL) {
4091 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4092 goto error;
4093 }
4094
4095 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", redis_req, ""},
4096 1, curpx, &rs->rules, file, line, &errmsg);
4097 if (!chk) {
4098 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4099 goto error;
4100 }
4101 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004102 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004103
4104 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "string", redis_res,
4105 "error-status", "L7STS",
4106 "on-error", "%[res.payload(0,0),cut_crlf]",
4107 "on-success", "Redis server is ok",
4108 ""},
4109 1, curpx, &rs->rules, TCPCHK_RULES_REDIS_CHK, file, line, &errmsg);
4110 if (!chk) {
4111 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4112 goto error;
4113 }
4114 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004115 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004116
4117 ruleset_found:
4118 rules->list = &rs->rules;
4119 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4120 rules->flags |= TCPCHK_RULES_REDIS_CHK;
4121
4122 out:
4123 free(errmsg);
4124 return err_code;
4125
4126 error:
4127 free_tcpcheck_ruleset(rs);
4128 err_code |= ERR_ALERT | ERR_FATAL;
4129 goto out;
4130}
4131
4132
4133/* Parses the "option ssl-hello-chk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004134int 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 +01004135 const char *file, int line)
4136{
4137 /* This is the SSLv3 CLIENT HELLO packet used in conjunction with the
4138 * ssl-hello-chk option to ensure that the remote server speaks SSL.
4139 *
4140 * Check RFC 2246 (TLSv1.0) sections A.3 and A.4 for details.
4141 */
4142 static char sslv3_client_hello[] = {
4143 "16" /* ContentType : 0x16 = Handshake */
4144 "0300" /* ProtocolVersion : 0x0300 = SSLv3 */
4145 "0079" /* ContentLength : 0x79 bytes after this one */
4146 "01" /* HanshakeType : 0x01 = CLIENT HELLO */
4147 "000075" /* HandshakeLength : 0x75 bytes after this one */
4148 "0300" /* Hello Version : 0x0300 = v3 */
4149 "%[date(),htonl,hex]" /* Unix GMT Time (s) : filled with <now> (@0x0B) */
4150 "%[str(HAPROXYSSLCHK\nHAPROXYSSLCHK\n),hex]" /* Random : must be exactly 28 bytes */
4151 "00" /* Session ID length : empty (no session ID) */
4152 "004E" /* Cipher Suite Length : 78 bytes after this one */
4153 "0001" "0002" "0003" "0004" /* 39 most common ciphers : */
4154 "0005" "0006" "0007" "0008" /* 0x01...0x1B, 0x2F...0x3A */
4155 "0009" "000A" "000B" "000C" /* This covers RSA/DH, */
4156 "000D" "000E" "000F" "0010" /* various bit lengths, */
4157 "0011" "0012" "0013" "0014" /* SHA1/MD5, DES/3DES/AES... */
4158 "0015" "0016" "0017" "0018"
4159 "0019" "001A" "001B" "002F"
4160 "0030" "0031" "0032" "0033"
4161 "0034" "0035" "0036" "0037"
4162 "0038" "0039" "003A"
4163 "01" /* Compression Length : 0x01 = 1 byte for types */
4164 "00" /* Compression Type : 0x00 = NULL compression */
4165 };
4166
4167 struct tcpcheck_ruleset *rs = NULL;
4168 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4169 struct tcpcheck_rule *chk;
4170 char *errmsg = NULL;
4171 int err_code = 0;
4172
4173 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4174 err_code |= ERR_WARN;
4175
4176 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4177 goto out;
4178
4179 curpx->options2 &= ~PR_O2_CHK_ANY;
4180 curpx->options2 |= PR_O2_TCPCHK_CHK;
4181
4182 free_tcpcheck_vars(&rules->preset_vars);
4183 rules->list = NULL;
4184 rules->flags = 0;
4185
4186 rs = find_tcpcheck_ruleset("*ssl-hello-check");
4187 if (rs)
4188 goto ruleset_found;
4189
4190 rs = create_tcpcheck_ruleset("*ssl-hello-check");
4191 if (rs == NULL) {
4192 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4193 goto error;
4194 }
4195
4196 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", sslv3_client_hello, ""},
4197 1, curpx, &rs->rules, file, line, &errmsg);
4198 if (!chk) {
4199 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4200 goto error;
4201 }
4202 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004203 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004204
4205 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^1[56]",
4206 "min-recv", "5", "ok-status", "L6OK",
4207 "error-status", "L6RSP", "tout-status", "L6TOUT",
4208 ""},
4209 1, curpx, &rs->rules, TCPCHK_RULES_SSL3_CHK, file, line, &errmsg);
4210 if (!chk) {
4211 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4212 goto error;
4213 }
4214 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004215 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004216
4217 ruleset_found:
4218 rules->list = &rs->rules;
4219 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4220 rules->flags |= TCPCHK_RULES_SSL3_CHK;
4221
4222 out:
4223 free(errmsg);
4224 return err_code;
4225
4226 error:
4227 free_tcpcheck_ruleset(rs);
4228 err_code |= ERR_ALERT | ERR_FATAL;
4229 goto out;
4230}
4231
4232/* Parses the "option smtpchk" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004233int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004234 const char *file, int line)
4235{
4236 static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
4237
4238 struct tcpcheck_ruleset *rs = NULL;
4239 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4240 struct tcpcheck_rule *chk;
4241 struct tcpcheck_var *var = NULL;
4242 char *cmd = NULL, *errmsg = NULL;
4243 int err_code = 0;
4244
4245 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4246 err_code |= ERR_WARN;
4247
4248 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4249 goto out;
4250
4251 curpx->options2 &= ~PR_O2_CHK_ANY;
4252 curpx->options2 |= PR_O2_TCPCHK_CHK;
4253
4254 free_tcpcheck_vars(&rules->preset_vars);
4255 rules->list = NULL;
4256 rules->flags = 0;
4257
4258 cur_arg += 2;
4259 if (*args[cur_arg] && *args[cur_arg+1] &&
4260 (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
4261 /* <EHLO|HELO> + space (1) + <host> + null byte (1) */
4262 cmd = calloc(strlen(args[cur_arg]) + 1 + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
4263 if (cmd)
4264 sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
4265 }
4266 else {
4267 /* this just hits the default for now, but you could potentially expand it to allow for other stuff
4268 though, it's unlikely you'd want to send anything other than an EHLO or HELO */
4269 cmd = strdup("HELO localhost");
4270 }
4271
4272 var = create_tcpcheck_var(ist("check.smtp_cmd"));
4273 if (cmd == NULL || var == NULL) {
4274 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4275 goto error;
4276 }
4277 var->data.type = SMP_T_STR;
4278 var->data.u.str.area = cmd;
4279 var->data.u.str.data = strlen(cmd);
4280 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004281 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004282 cmd = NULL;
4283 var = NULL;
4284
4285 rs = find_tcpcheck_ruleset("*smtp-check");
4286 if (rs)
4287 goto ruleset_found;
4288
4289 rs = create_tcpcheck_ruleset("*smtp-check");
4290 if (rs == NULL) {
4291 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4292 goto error;
4293 }
4294
4295 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4296 1, curpx, &rs->rules, file, line, &errmsg);
4297 if (!chk) {
4298 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4299 goto error;
4300 }
4301 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004302 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004303
4304 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
4305 "min-recv", "4",
4306 "error-status", "L7RSP",
4307 "on-error", "%[res.payload(0,0),cut_crlf]",
4308 ""},
4309 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4310 if (!chk) {
4311 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4312 goto error;
4313 }
4314 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004315 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004316
4317 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
4318 "min-recv", "4",
4319 "error-status", "L7STS",
4320 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4321 "status-code", "res.payload(0,3)",
4322 ""},
4323 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4324 if (!chk) {
4325 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4326 goto error;
4327 }
4328 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004329 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004330
4331 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-lf", smtp_req, ""},
4332 1, curpx, &rs->rules, file, line, &errmsg);
4333 if (!chk) {
4334 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4335 goto error;
4336 }
4337 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004338 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004339
4340 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
4341 "min-recv", "4",
4342 "error-status", "L7STS",
4343 "on-error", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4344 "on-success", "%[res.payload(4,0),ltrim(' '),cut_crlf]",
4345 "status-code", "res.payload(0,3)",
4346 ""},
4347 1, curpx, &rs->rules, TCPCHK_RULES_SMTP_CHK, file, line, &errmsg);
4348 if (!chk) {
4349 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4350 goto error;
4351 }
4352 chk->index = 4;
Willy Tarreau2b718102021-04-21 07:32:39 +02004353 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004354
4355 ruleset_found:
4356 rules->list = &rs->rules;
4357 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4358 rules->flags |= TCPCHK_RULES_SMTP_CHK;
4359
4360 out:
4361 free(errmsg);
4362 return err_code;
4363
4364 error:
4365 free(cmd);
4366 free(var);
4367 free_tcpcheck_vars(&rules->preset_vars);
4368 free_tcpcheck_ruleset(rs);
4369 err_code |= ERR_ALERT | ERR_FATAL;
4370 goto out;
4371}
4372
4373/* Parses the "option pgsql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004374int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004375 const char *file, int line)
4376{
4377 static char pgsql_req[] = {
4378 "%[var(check.plen),htonl,hex]" /* The packet length*/
4379 "00030000" /* the version 3.0 */
4380 "7573657200" /* "user" key */
4381 "%[var(check.username),hex]00" /* the username */
4382 "00"
4383 };
4384
4385 struct tcpcheck_ruleset *rs = NULL;
4386 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4387 struct tcpcheck_rule *chk;
4388 struct tcpcheck_var *var = NULL;
4389 char *user = NULL, *errmsg = NULL;
4390 size_t packetlen = 0;
4391 int err_code = 0;
4392
4393 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4394 err_code |= ERR_WARN;
4395
4396 if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
4397 goto out;
4398
4399 curpx->options2 &= ~PR_O2_CHK_ANY;
4400 curpx->options2 |= PR_O2_TCPCHK_CHK;
4401
4402 free_tcpcheck_vars(&rules->preset_vars);
4403 rules->list = NULL;
4404 rules->flags = 0;
4405
4406 cur_arg += 2;
4407 if (!*args[cur_arg] || !*args[cur_arg+1]) {
4408 ha_alert("parsing [%s:%d] : '%s %s' expects 'user <username>' as argument.\n",
4409 file, line, args[0], args[1]);
4410 goto error;
4411 }
4412 if (strcmp(args[cur_arg], "user") == 0) {
4413 packetlen = 15 + strlen(args[cur_arg+1]);
4414 user = strdup(args[cur_arg+1]);
4415
4416 var = create_tcpcheck_var(ist("check.username"));
4417 if (user == NULL || var == NULL) {
4418 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4419 goto error;
4420 }
4421 var->data.type = SMP_T_STR;
4422 var->data.u.str.area = user;
4423 var->data.u.str.data = strlen(user);
4424 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004425 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004426 user = NULL;
4427 var = NULL;
4428
4429 var = create_tcpcheck_var(ist("check.plen"));
4430 if (var == NULL) {
4431 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4432 goto error;
4433 }
4434 var->data.type = SMP_T_SINT;
4435 var->data.u.sint = packetlen;
4436 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004437 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004438 var = NULL;
4439 }
4440 else {
4441 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
4442 file, line, args[0], args[1]);
4443 goto error;
4444 }
4445
4446 rs = find_tcpcheck_ruleset("*pgsql-check");
4447 if (rs)
4448 goto ruleset_found;
4449
4450 rs = create_tcpcheck_ruleset("*pgsql-check");
4451 if (rs == NULL) {
4452 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4453 goto error;
4454 }
4455
4456 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4457 1, curpx, &rs->rules, file, line, &errmsg);
4458 if (!chk) {
4459 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4460 goto error;
4461 }
4462 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004463 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004464
4465 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", pgsql_req, ""},
4466 1, curpx, &rs->rules, file, line, &errmsg);
4467 if (!chk) {
4468 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4469 goto error;
4470 }
4471 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004472 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004473
4474 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E",
4475 "min-recv", "5",
4476 "error-status", "L7RSP",
4477 "on-error", "%[res.payload(6,0)]",
4478 ""},
4479 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4480 if (!chk) {
4481 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4482 goto error;
4483 }
4484 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004485 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004486
4487 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^52000000(08|0A|0C)000000(00|02|03|04|05|06)",
4488 "min-recv", "9",
4489 "error-status", "L7STS",
4490 "on-success", "PostgreSQL server is ok",
4491 "on-error", "PostgreSQL unknown error",
4492 ""},
4493 1, curpx, &rs->rules, TCPCHK_RULES_PGSQL_CHK, file, line, &errmsg);
4494 if (!chk) {
4495 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4496 goto error;
4497 }
4498 chk->index = 3;
Willy Tarreau2b718102021-04-21 07:32:39 +02004499 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004500
4501 ruleset_found:
4502 rules->list = &rs->rules;
4503 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4504 rules->flags |= TCPCHK_RULES_PGSQL_CHK;
4505
4506 out:
4507 free(errmsg);
4508 return err_code;
4509
4510 error:
4511 free(user);
4512 free(var);
4513 free_tcpcheck_vars(&rules->preset_vars);
4514 free_tcpcheck_ruleset(rs);
4515 err_code |= ERR_ALERT | ERR_FATAL;
4516 goto out;
4517}
4518
4519
4520/* Parses the "option mysql-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004521int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004522 const char *file, int line)
4523{
4524 /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
4525 * const char mysql40_client_auth_pkt[] = {
4526 * "\x0e\x00\x00" // packet length
4527 * "\x01" // packet number
4528 * "\x00\x00" // client capabilities
4529 * "\x00\x00\x01" // max packet
4530 * "haproxy\x00" // username (null terminated string)
4531 * "\x00" // filler (always 0x00)
4532 * "\x01\x00\x00" // packet length
4533 * "\x00" // packet number
4534 * "\x01" // COM_QUIT command
4535 * };
4536 */
4537 static char mysql40_rsname[] = "*mysql40-check";
4538 static char mysql40_req[] = {
4539 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4540 "0080" /* client capabilities */
4541 "000001" /* max packet */
4542 "%[var(check.username),hex]00" /* the username */
4543 "00" /* filler (always 0x00) */
4544 "010000" /* packet length*/
4545 "00" /* sequence ID */
4546 "01" /* COM_QUIT command */
4547 };
4548
4549 /* This is an example of a MySQL >=4.1 client Authentication packet provided by Nenad Merdanovic.
4550 * const char mysql41_client_auth_pkt[] = {
4551 * "\x0e\x00\x00\" // packet length
4552 * "\x01" // packet number
4553 * "\x00\x00\x00\x00" // client capabilities
4554 * "\x00\x00\x00\x01" // max packet
4555 * "\x21" // character set (UTF-8)
4556 * char[23] // All zeroes
4557 * "haproxy\x00" // username (null terminated string)
4558 * "\x00" // filler (always 0x00)
4559 * "\x01\x00\x00" // packet length
4560 * "\x00" // packet number
4561 * "\x01" // COM_QUIT command
4562 * };
4563 */
4564 static char mysql41_rsname[] = "*mysql41-check";
4565 static char mysql41_req[] = {
4566 "%[var(check.header),hex]" /* 3 bytes for the packet length and 1 byte for the sequence ID */
4567 "00820000" /* client capabilities */
4568 "00800001" /* max packet */
4569 "21" /* character set (UTF-8) */
4570 "000000000000000000000000" /* 23 bytes, al zeroes */
4571 "0000000000000000000000"
4572 "%[var(check.username),hex]00" /* the username */
4573 "00" /* filler (always 0x00) */
4574 "010000" /* packet length*/
4575 "00" /* sequence ID */
4576 "01" /* COM_QUIT command */
4577 };
4578
4579 struct tcpcheck_ruleset *rs = NULL;
4580 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4581 struct tcpcheck_rule *chk;
4582 struct tcpcheck_var *var = NULL;
4583 char *mysql_rsname = "*mysql-check";
4584 char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
4585 int index = 0, err_code = 0;
4586
4587 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4588 err_code |= ERR_WARN;
4589
4590 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
4591 goto out;
4592
4593 curpx->options2 &= ~PR_O2_CHK_ANY;
4594 curpx->options2 |= PR_O2_TCPCHK_CHK;
4595
4596 free_tcpcheck_vars(&rules->preset_vars);
4597 rules->list = NULL;
4598 rules->flags = 0;
4599
4600 cur_arg += 2;
4601 if (*args[cur_arg]) {
4602 int packetlen, userlen;
4603
4604 if (strcmp(args[cur_arg], "user") != 0) {
4605 ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
4606 file, line, args[0], args[1], args[cur_arg]);
4607 goto error;
4608 }
4609
4610 if (*(args[cur_arg+1]) == 0) {
4611 ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
4612 file, line, args[0], args[1], args[cur_arg]);
4613 goto error;
4614 }
4615
4616 hdr = calloc(4, sizeof(*hdr));
4617 user = strdup(args[cur_arg+1]);
4618 userlen = strlen(args[cur_arg+1]);
4619
4620 if (hdr == NULL || user == NULL) {
4621 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4622 goto error;
4623 }
4624
4625 if (!*args[cur_arg+2] || strcmp(args[cur_arg+2], "post-41") == 0) {
4626 packetlen = userlen + 7 + 27;
4627 mysql_req = mysql41_req;
4628 mysql_rsname = mysql41_rsname;
4629 }
4630 else if (strcmp(args[cur_arg+2], "pre-41") == 0) {
4631 packetlen = userlen + 7;
4632 mysql_req = mysql40_req;
4633 mysql_rsname = mysql40_rsname;
4634 }
4635 else {
4636 ha_alert("parsing [%s:%d] : keyword '%s' only supports 'post-41' and 'pre-41' (got '%s').\n",
4637 file, line, args[cur_arg], args[cur_arg+2]);
4638 goto error;
4639 }
4640
4641 hdr[0] = (unsigned char)(packetlen & 0xff);
4642 hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
4643 hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
4644 hdr[3] = 1;
4645
4646 var = create_tcpcheck_var(ist("check.header"));
4647 if (var == NULL) {
4648 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4649 goto error;
4650 }
4651 var->data.type = SMP_T_STR;
4652 var->data.u.str.area = hdr;
4653 var->data.u.str.data = 4;
4654 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004655 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004656 hdr = NULL;
4657 var = NULL;
4658
4659 var = create_tcpcheck_var(ist("check.username"));
4660 if (var == NULL) {
4661 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4662 goto error;
4663 }
4664 var->data.type = SMP_T_STR;
4665 var->data.u.str.area = user;
4666 var->data.u.str.data = strlen(user);
4667 LIST_INIT(&var->list);
Willy Tarreau2b718102021-04-21 07:32:39 +02004668 LIST_APPEND(&rules->preset_vars, &var->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004669 user = NULL;
4670 var = NULL;
4671 }
4672
4673 rs = find_tcpcheck_ruleset(mysql_rsname);
4674 if (rs)
4675 goto ruleset_found;
4676
4677 rs = create_tcpcheck_ruleset(mysql_rsname);
4678 if (rs == NULL) {
4679 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4680 goto error;
4681 }
4682
4683 chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
4684 1, curpx, &rs->rules, file, line, &errmsg);
4685 if (!chk) {
4686 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4687 goto error;
4688 }
4689 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004690 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004691
4692 if (mysql_req) {
4693 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary-lf", mysql_req, ""},
4694 1, curpx, &rs->rules, file, line, &errmsg);
4695 if (!chk) {
4696 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4697 goto error;
4698 }
4699 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004700 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004701 }
4702
4703 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4704 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4705 if (!chk) {
4706 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4707 goto error;
4708 }
4709 chk->expect.custom = tcpcheck_mysql_expect_iniths;
4710 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004711 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004712
4713 if (mysql_req) {
4714 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4715 1, curpx, &rs->rules, TCPCHK_RULES_MYSQL_CHK, file, line, &errmsg);
4716 if (!chk) {
4717 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4718 goto error;
4719 }
4720 chk->expect.custom = tcpcheck_mysql_expect_ok;
4721 chk->index = index++;
Willy Tarreau2b718102021-04-21 07:32:39 +02004722 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004723 }
4724
4725 ruleset_found:
4726 rules->list = &rs->rules;
4727 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4728 rules->flags |= TCPCHK_RULES_MYSQL_CHK;
4729
4730 out:
4731 free(errmsg);
4732 return err_code;
4733
4734 error:
4735 free(hdr);
4736 free(user);
4737 free(var);
4738 free_tcpcheck_vars(&rules->preset_vars);
4739 free_tcpcheck_ruleset(rs);
4740 err_code |= ERR_ALERT | ERR_FATAL;
4741 goto out;
4742}
4743
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004744int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004745 const char *file, int line)
4746{
4747 static char *ldap_req = "300C020101600702010304008000";
4748
4749 struct tcpcheck_ruleset *rs = NULL;
4750 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4751 struct tcpcheck_rule *chk;
4752 char *errmsg = NULL;
4753 int err_code = 0;
4754
4755 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4756 err_code |= ERR_WARN;
4757
4758 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4759 goto out;
4760
4761 curpx->options2 &= ~PR_O2_CHK_ANY;
4762 curpx->options2 |= PR_O2_TCPCHK_CHK;
4763
4764 free_tcpcheck_vars(&rules->preset_vars);
4765 rules->list = NULL;
4766 rules->flags = 0;
4767
4768 rs = find_tcpcheck_ruleset("*ldap-check");
4769 if (rs)
4770 goto ruleset_found;
4771
4772 rs = create_tcpcheck_ruleset("*ldap-check");
4773 if (rs == NULL) {
4774 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4775 goto error;
4776 }
4777
4778 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", ldap_req, ""},
4779 1, curpx, &rs->rules, file, line, &errmsg);
4780 if (!chk) {
4781 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4782 goto error;
4783 }
4784 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004785 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004786
4787 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^30",
4788 "min-recv", "14",
4789 "on-error", "Not LDAPv3 protocol",
4790 ""},
4791 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4792 if (!chk) {
4793 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4794 goto error;
4795 }
4796 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004797 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004798
4799 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
4800 1, curpx, &rs->rules, TCPCHK_RULES_LDAP_CHK, file, line, &errmsg);
4801 if (!chk) {
4802 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4803 goto error;
4804 }
4805 chk->expect.custom = tcpcheck_ldap_expect_bindrsp;
4806 chk->index = 2;
Willy Tarreau2b718102021-04-21 07:32:39 +02004807 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004808
4809 ruleset_found:
4810 rules->list = &rs->rules;
4811 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4812 rules->flags |= TCPCHK_RULES_LDAP_CHK;
4813
4814 out:
4815 free(errmsg);
4816 return err_code;
4817
4818 error:
4819 free_tcpcheck_ruleset(rs);
4820 err_code |= ERR_ALERT | ERR_FATAL;
4821 goto out;
4822}
4823
Willy Tarreau54fa7e32021-02-12 12:09:38 +01004824int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004825 const char *file, int line)
4826{
4827 struct tcpcheck_ruleset *rs = NULL;
4828 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
4829 struct tcpcheck_rule *chk;
4830 char *spop_req = NULL;
4831 char *errmsg = NULL;
4832 int spop_len = 0, err_code = 0;
4833
4834 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
4835 err_code |= ERR_WARN;
4836
4837 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
4838 goto out;
4839
4840 curpx->options2 &= ~PR_O2_CHK_ANY;
4841 curpx->options2 |= PR_O2_TCPCHK_CHK;
4842
4843 free_tcpcheck_vars(&rules->preset_vars);
4844 rules->list = NULL;
4845 rules->flags = 0;
4846
4847
4848 rs = find_tcpcheck_ruleset("*spop-check");
4849 if (rs)
4850 goto ruleset_found;
4851
4852 rs = create_tcpcheck_ruleset("*spop-check");
4853 if (rs == NULL) {
4854 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4855 goto error;
4856 }
4857
4858 if (spoe_prepare_healthcheck_request(&spop_req, &spop_len) == -1) {
4859 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
4860 goto error;
4861 }
4862 chunk_reset(&trash);
4863 dump_binary(&trash, spop_req, spop_len);
4864 trash.area[trash.data] = '\0';
4865
4866 chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", b_head(&trash), ""},
4867 1, curpx, &rs->rules, file, line, &errmsg);
4868 if (!chk) {
4869 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4870 goto error;
4871 }
4872 chk->index = 0;
Willy Tarreau2b718102021-04-21 07:32:39 +02004873 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004874
4875 chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", "min-recv", "4", ""},
4876 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg);
4877 if (!chk) {
4878 ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
4879 goto error;
4880 }
4881 chk->expect.custom = tcpcheck_spop_expect_agenthello;
4882 chk->index = 1;
Willy Tarreau2b718102021-04-21 07:32:39 +02004883 LIST_APPEND(&rs->rules, &chk->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004884
4885 ruleset_found:
4886 rules->list = &rs->rules;
4887 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
4888 rules->flags |= TCPCHK_RULES_SPOP_CHK;
4889
4890 out:
4891 free(spop_req);
4892 free(errmsg);
4893 return err_code;
4894
4895 error:
4896 free_tcpcheck_ruleset(rs);
4897 err_code |= ERR_ALERT | ERR_FATAL;
4898 goto out;
4899}
4900
4901
4902static struct tcpcheck_rule *proxy_parse_httpchk_req(char **args, int cur_arg, struct proxy *px, char **errmsg)
4903{
4904 struct tcpcheck_rule *chk = NULL;
4905 struct tcpcheck_http_hdr *hdr = NULL;
4906 char *meth = NULL, *uri = NULL, *vsn = NULL;
4907 char *hdrs, *body;
4908
4909 hdrs = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n") : NULL);
4910 body = (*args[cur_arg+2] ? strstr(args[cur_arg+2], "\r\n\r\n") : NULL);
4911 if (hdrs == body)
4912 hdrs = NULL;
4913 if (hdrs) {
4914 *hdrs = '\0';
4915 hdrs +=2;
4916 }
4917 if (body) {
4918 *body = '\0';
4919 body += 4;
4920 }
4921 if (hdrs || body) {
4922 memprintf(errmsg, "hiding headers or body at the end of the version string is deprecated."
4923 " Please, consider to use 'http-check send' directive instead.");
4924 }
4925
4926 chk = calloc(1, sizeof(*chk));
4927 if (!chk) {
4928 memprintf(errmsg, "out of memory");
4929 goto error;
4930 }
4931 chk->action = TCPCHK_ACT_SEND;
4932 chk->send.type = TCPCHK_SEND_HTTP;
4933 chk->send.http.flags |= TCPCHK_SND_HTTP_FROM_OPT;
4934 chk->send.http.meth.meth = HTTP_METH_OPTIONS;
4935 LIST_INIT(&chk->send.http.hdrs);
4936
4937 /* Copy the method, uri and version */
4938 if (*args[cur_arg]) {
4939 if (!*args[cur_arg+1])
4940 uri = args[cur_arg];
4941 else
4942 meth = args[cur_arg];
4943 }
4944 if (*args[cur_arg+1])
4945 uri = args[cur_arg+1];
4946 if (*args[cur_arg+2])
4947 vsn = args[cur_arg+2];
4948
4949 if (meth) {
4950 chk->send.http.meth.meth = find_http_meth(meth, strlen(meth));
4951 chk->send.http.meth.str.area = strdup(meth);
4952 chk->send.http.meth.str.data = strlen(meth);
4953 if (!chk->send.http.meth.str.area) {
4954 memprintf(errmsg, "out of memory");
4955 goto error;
4956 }
4957 }
4958 if (uri) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004959 chk->send.http.uri = ist(strdup(uri));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004960 if (!isttest(chk->send.http.uri)) {
4961 memprintf(errmsg, "out of memory");
4962 goto error;
4963 }
4964 }
4965 if (vsn) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01004966 chk->send.http.vsn = ist(strdup(vsn));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01004967 if (!isttest(chk->send.http.vsn)) {
4968 memprintf(errmsg, "out of memory");
4969 goto error;
4970 }
4971 }
4972
4973 /* Copy the header */
4974 if (hdrs) {
4975 struct http_hdr tmp_hdrs[global.tune.max_http_hdr];
4976 struct h1m h1m;
4977 int i, ret;
4978
4979 /* Build and parse the request */
4980 chunk_printf(&trash, "%s\r\n\r\n", hdrs);
4981
4982 h1m.flags = H1_MF_HDRS_ONLY;
4983 ret = h1_headers_to_hdr_list(b_orig(&trash), b_tail(&trash),
4984 tmp_hdrs, sizeof(tmp_hdrs)/sizeof(tmp_hdrs[0]),
4985 &h1m, NULL);
4986 if (ret <= 0) {
4987 memprintf(errmsg, "unable to parse the request '%s'.", b_orig(&trash));
4988 goto error;
4989 }
4990
4991 for (i = 0; istlen(tmp_hdrs[i].n); i++) {
4992 hdr = calloc(1, sizeof(*hdr));
4993 if (!hdr) {
4994 memprintf(errmsg, "out of memory");
4995 goto error;
4996 }
4997 LIST_INIT(&hdr->value);
4998 hdr->name = istdup(tmp_hdrs[i].n);
Tim Duesterhus77508502022-03-15 13:11:06 +01004999 if (!isttest(hdr->name)) {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005000 memprintf(errmsg, "out of memory");
5001 goto error;
5002 }
5003
5004 ist0(tmp_hdrs[i].v);
5005 if (!parse_logformat_string(istptr(tmp_hdrs[i].v), px, &hdr->value, 0, SMP_VAL_BE_CHK_RUL, errmsg))
5006 goto error;
Willy Tarreau2b718102021-04-21 07:32:39 +02005007 LIST_APPEND(&chk->send.http.hdrs, &hdr->list);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005008 }
5009 }
5010
5011 /* Copy the body */
5012 if (body) {
Tim Duesterhusdcf753a2021-03-04 17:31:47 +01005013 chk->send.http.body = ist(strdup(body));
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005014 if (!isttest(chk->send.http.body)) {
5015 memprintf(errmsg, "out of memory");
5016 goto error;
5017 }
5018 }
5019
5020 return chk;
5021
5022 error:
5023 free_tcpcheck_http_hdr(hdr);
5024 free_tcpcheck(chk, 0);
5025 return NULL;
5026}
5027
5028/* Parses the "option httpchck" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005029int proxy_parse_httpchk_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005030 const char *file, int line)
5031{
5032 struct tcpcheck_ruleset *rs = NULL;
5033 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5034 struct tcpcheck_rule *chk;
5035 char *errmsg = NULL;
5036 int err_code = 0;
5037
5038 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5039 err_code |= ERR_WARN;
5040
5041 if (alertif_too_many_args_idx(3, 1, file, line, args, &err_code))
5042 goto out;
5043
5044 chk = proxy_parse_httpchk_req(args, cur_arg+2, curpx, &errmsg);
5045 if (!chk) {
5046 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5047 goto error;
5048 }
5049 if (errmsg) {
5050 ha_warning("parsing [%s:%d]: '%s %s' : %s\n", file, line, args[0], args[1], errmsg);
5051 err_code |= ERR_WARN;
Willy Tarreau61cfdf42021-02-20 10:46:51 +01005052 ha_free(&errmsg);
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005053 }
5054
5055 no_request:
5056 curpx->options2 &= ~PR_O2_CHK_ANY;
5057 curpx->options2 |= PR_O2_TCPCHK_CHK;
5058
5059 free_tcpcheck_vars(&rules->preset_vars);
5060 rules->list = NULL;
5061 rules->flags |= TCPCHK_SND_HTTP_FROM_OPT;
5062
5063 /* Deduce the ruleset name from the proxy info */
5064 chunk_printf(&trash, "*http-check-%s_%s-%d",
5065 ((curpx == defpx) ? "defaults" : curpx->id),
5066 curpx->conf.file, curpx->conf.line);
5067
5068 rs = find_tcpcheck_ruleset(b_orig(&trash));
5069 if (rs == NULL) {
5070 rs = create_tcpcheck_ruleset(b_orig(&trash));
5071 if (rs == NULL) {
5072 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5073 goto error;
5074 }
5075 }
5076
5077 rules->list = &rs->rules;
5078 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5079 rules->flags |= TCPCHK_RULES_HTTP_CHK;
5080 if (!tcpcheck_add_http_rule(chk, rules, &errmsg)) {
5081 ha_alert("parsing [%s:%d] : '%s %s' : %s.\n", file, line, args[0], args[1], errmsg);
5082 rules->list = NULL;
5083 goto error;
5084 }
5085
5086 out:
5087 free(errmsg);
5088 return err_code;
5089
5090 error:
5091 free_tcpcheck_ruleset(rs);
5092 free_tcpcheck(chk, 0);
5093 err_code |= ERR_ALERT | ERR_FATAL;
5094 goto out;
5095}
5096
5097/* Parses the "option tcp-check" proxy keyword */
Willy Tarreau54fa7e32021-02-12 12:09:38 +01005098int proxy_parse_tcp_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx,
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005099 const char *file, int line)
5100{
5101 struct tcpcheck_ruleset *rs = NULL;
5102 struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
5103 int err_code = 0;
5104
5105 if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
5106 err_code |= ERR_WARN;
5107
5108 if (alertif_too_many_args_idx(0, 1, file, line, args, &err_code))
5109 goto out;
5110
5111 curpx->options2 &= ~PR_O2_CHK_ANY;
5112 curpx->options2 |= PR_O2_TCPCHK_CHK;
5113
5114 if ((rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_TCP_CHK) {
5115 /* If a tcp-check rulesset is already set, do nothing */
5116 if (rules->list)
5117 goto out;
5118
5119 /* If a tcp-check ruleset is waiting to be used for the current proxy,
5120 * get it.
5121 */
5122 if (rules->flags & TCPCHK_RULES_UNUSED_TCP_RS)
5123 goto curpx_ruleset;
5124
5125 /* Otherwise, try to get the tcp-check ruleset of the default proxy */
5126 chunk_printf(&trash, "*tcp-check-defaults_%s-%d", defpx->conf.file, defpx->conf.line);
5127 rs = find_tcpcheck_ruleset(b_orig(&trash));
5128 if (rs)
5129 goto ruleset_found;
5130 }
5131
5132 curpx_ruleset:
5133 /* Deduce the ruleset name from the proxy info */
5134 chunk_printf(&trash, "*tcp-check-%s_%s-%d",
5135 ((curpx == defpx) ? "defaults" : curpx->id),
5136 curpx->conf.file, curpx->conf.line);
5137
5138 rs = find_tcpcheck_ruleset(b_orig(&trash));
5139 if (rs == NULL) {
5140 rs = create_tcpcheck_ruleset(b_orig(&trash));
5141 if (rs == NULL) {
5142 ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
5143 goto error;
5144 }
5145 }
5146
5147 ruleset_found:
5148 free_tcpcheck_vars(&rules->preset_vars);
5149 rules->list = &rs->rules;
5150 rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);
5151 rules->flags |= TCPCHK_RULES_TCP_CHK;
5152
5153 out:
5154 return err_code;
5155
5156 error:
5157 err_code |= ERR_ALERT | ERR_FATAL;
5158 goto out;
5159}
5160
Willy Tarreau51cd5952020-06-05 12:25:38 +02005161static struct cfg_kw_list cfg_kws = {ILH, {
Christopher Faulet97b7bdf2020-11-27 09:58:02 +01005162 { CFG_LISTEN, "http-check", proxy_parse_httpcheck },
Willy Tarreau51cd5952020-06-05 12:25:38 +02005163 { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck },
5164 { 0, NULL, NULL },
5165}};
5166
5167REGISTER_POST_PROXY_CHECK(check_proxy_tcpcheck);
5168REGISTER_PROXY_DEINIT(deinit_proxy_tcpcheck);
5169REGISTER_POST_DEINIT(deinit_tcpchecks);
5170INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);