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