]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: checks: Implement smtp check using tcp-check rules
authorChristopher Faulet <cfaulet@haproxy.com>
Wed, 1 Apr 2020 18:54:05 +0000 (20:54 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 27 Apr 2020 07:39:38 +0000 (09:39 +0200)
A share tcp-check ruleset is now created to support smtp checks. This way no
extra memory is used if several backends use a smtp check.

The following sequence is used :

    tcp-check connect default linger

    tcp-check expect rstring "^[0-9]{3}[ \r]" min-recv 4 \
        error-status "L7RSP" on-error "%[check.payload(),cut_crlf]"

    tcp-check expect rstring "^2[0-9]{2}[ \r]" min-recv 4 \
        error-status "L7STS" \
        on-error %[check.payload(4,0),ltrim(' '),cut_crlf] \
        status-code "check.payload(0,3)"

    tcp-echeck send "%[var(check.smtp_cmd)]\r\n" log-format

    tcp-check expect rstring "^2[0-9]{2}[- \r]" min-recv 4 \
        error-status "L7STS" \
        on-error %[check.payload(4,0),ltrim(' '),cut_crlf] \
        on-success "%[check.payload(4,0),ltrim(' '),cut_crlf]" \
        status-code "check.payload(0,3)"

The variable check.smtp_cmd is by default the string "HELO localhost" by may be
customized setting <helo> and <domain> parameters on the option smtpchk
line. Note there is a difference with the old smtp check. The server gretting
message is checked before send the HELO/EHLO comand.

include/common/defaults.h
include/proto/checks.h
include/types/checks.h
include/types/proxy.h
src/cfgparse-listen.c
src/checks.c

index d52e00580cc20a320d46109340460c877f528503..b63870e658207995560023f8d985cb591f53a466 100644 (file)
 #define DEF_AGENT_RISETIME    1
 #define DEF_CHECK_REQ   "OPTIONS / HTTP/1.0\r\n"
 #define DEF_CHECK_PATH  ""
-#define DEF_SMTP_CHECK_REQ   "HELO localhost\r\n"
 #define DEF_LDAP_CHECK_REQ   "\x30\x0c\x02\x01\x01\x60\x07\x02\x01\x03\x04\x00\x80\x00"
 
 
index 33ed492c46096e01156a415282a91ca70ec5f8ac..b3ea9175bfe315d8082fbd224a6994f47c585d99 100644 (file)
@@ -3,7 +3,7 @@
   Functions prototypes for the checks.
 
   Copyright (C) 2000-2009 Willy Tarreau - w@1wt.eu
-  
+
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation, version 2.1
@@ -73,6 +73,8 @@ int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, s
                                const char *file, int line);
 int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
                                  const char *file, int line);
+int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
+                           const char *file, int line);
 
 #endif /* _PROTO_CHECKS_H */
 
index 4830f0d945fe0f5f52b58cd04ede3a3d26bb2d38..541666c664b6da0c986a5c7fe65bbe0e788663e0 100644 (file)
@@ -313,6 +313,7 @@ struct tcpcheck_rule {
 #define TCPCHK_RULES_DEF         0x00000002 /* Ruleset inherited from the default section */
 
 #define TCPCHK_RULES_REDIS_CHK   0x00000020
+#define TCPCHK_RULES_SMTP_CHK    0x00000030
 #define TCPCHK_RULES_SSL3_CHK    0x00000070
 
 /* A list of tcp-check vars, to be registered before executing a ruleset */
index 9ace915d9ab0c0770dcab41fc349c6ed4773852a..0f4301d272bef626a606753e97b4f22694652c36 100644 (file)
@@ -171,7 +171,7 @@ enum PR_SRV_STATE_FILE {
 #define PR_O2_CHK_NONE  0x00000000      /* no L7 health checks configured (TCP by default) */
 #define PR_O2_PGSQL_CHK 0x10000000      /* use PGSQL check for server health */
 /* unused: 0x20000000 */
-#define PR_O2_SMTP_CHK  0x30000000      /* use SMTP EHLO check for server health - pvandijk@vision6.com.au */
+/* unused  0x30000000 */
 #define PR_O2_HTTP_CHK  0x40000000      /* use HTTP 'OPTIONS' method to check server health */
 #define PR_O2_MYSQL_CHK 0x50000000      /* use MYSQL check for server health */
 #define PR_O2_LDAP_CHK  0x60000000      /* use LDAP check for server health */
index 291dca80548b28945dd6383ef3f9101d810db093..e85cde2d51f40160804dc03eeb83f0cdd3a4535f 100644 (file)
@@ -2402,29 +2402,8 @@ stats_error_parsing:
                                goto out;
                }
                else if (!strcmp(args[1], "smtpchk")) {
-                       /* use SMTP request to check servers' health */
-                       free(curproxy->check_req);
-                       curproxy->check_req = NULL;
-                       curproxy->options2 &= ~PR_O2_CHK_ANY;
-                       curproxy->options2 |= PR_O2_SMTP_CHK;
-
-                       if (!*args[2] || !*args[3]) { /* no argument or incomplete EHLO host */
-                               curproxy->check_req = strdup(DEF_SMTP_CHECK_REQ); /* default request */
-                               curproxy->check_len = strlen(DEF_SMTP_CHECK_REQ);
-                       } else { /* ESMTP EHLO, or SMTP HELO, and a hostname */
-                               if (!strcmp(args[2], "EHLO") || !strcmp(args[2], "HELO")) {
-                                       int reqlen = strlen(args[2]) + strlen(args[3]) + strlen(" \r\n") + 1;
-                                       curproxy->check_req = malloc(reqlen);
-                                       curproxy->check_len = snprintf(curproxy->check_req, reqlen,
-                                                                      "%s %s\r\n", args[2], args[3]); /* HELO hostname */
-                               } else {
-                                       /* this just hits the default for now, but you could potentially expand it to allow for other stuff
-                                          though, it's unlikely you'd want to send anything other than an EHLO or HELO */
-                                       curproxy->check_req = strdup(DEF_SMTP_CHECK_REQ); /* default request */
-                                       curproxy->check_len = strlen(DEF_SMTP_CHECK_REQ);
-                               }
-                       }
-                       if (alertif_too_many_args_idx(2, 1, file, linenum, args, &err_code))
+                       err_code |= proxy_parse_smtpchk_opt(args, 0, curproxy, &defproxy, file, linenum);
+                       if (err_code & ERR_FATAL)
                                goto out;
                }
                else if (!strcmp(args[1], "pgsql-check")) {
index 0c62a03b05fa980c8835eee9b1b0a87e6892df6c..b0e770480373ddd63260728c16edad3fb3c8b038 100644 (file)
@@ -949,36 +949,6 @@ static void __event_srv_chk_r(struct conn_stream *cs)
                }
                break;
 
-       case PR_O2_SMTP_CHK:
-               if (!done && b_data(&check->bi) < strlen("000\r"))
-                       goto wait_more_data;
-
-               /* do not reset when closing, servers don't like this */
-               if (conn_ctrl_ready(cs->conn))
-                       fdtab[cs->conn->handle.fd].linger_risk = 0;
-
-               /* Check if the server speaks SMTP */
-               if ((b_data(&check->bi) < strlen("000\r")) ||
-                   (*(b_head(&check->bi) + 3) != ' ' && *(b_head(&check->bi) + 3) != '\r') ||
-                   !isdigit((unsigned char) *b_head(&check->bi)) || !isdigit((unsigned char) *(b_head(&check->bi) + 1)) ||
-                   !isdigit((unsigned char) *(b_head(&check->bi) + 2))) {
-                       cut_crlf(b_head(&check->bi));
-                       set_server_check_status(check, HCHK_STATUS_L7RSP, b_head(&check->bi));
-                       goto out_wakeup;
-               }
-
-               check->code = str2uic(b_head(&check->bi));
-
-               desc = ltrim(b_head(&check->bi) + 3, ' ');
-               cut_crlf(desc);
-
-               /* Check for SMTP code 2xx (should be 250) */
-               if (*b_head(&check->bi) == '2')
-                       set_server_check_status(check, HCHK_STATUS_L7OKD, desc);
-               else
-                       set_server_check_status(check, HCHK_STATUS_L7STS, desc);
-               break;
-
        case PR_O2_LB_AGENT_CHK: {
                int status = HCHK_STATUS_CHECKED;
                const char *hs = NULL; /* health status      */
@@ -5282,6 +5252,153 @@ int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx,
        goto out;
 }
 
+/* Parses the "option smtpchk" proxy keyword */
+int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
+                           const char *file, int line)
+{
+       static char *smtp_req = "%[var(check.smtp_cmd)]\r\n";
+
+       struct tcpcheck_ruleset *rs = NULL;
+       struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
+       struct tcpcheck_rule *chk;
+       struct tcpcheck_var *var = NULL;
+       char *cmd = NULL, *errmsg = NULL;
+       int err_code = 0;
+
+       if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL))
+               err_code |= ERR_WARN;
+
+       if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code))
+               goto out;
+
+       if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) {
+               ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n",
+                        file, line);
+               err_code |= ERR_ALERT | ERR_FATAL;
+               goto out;
+       }
+
+       curpx->options2 &= ~PR_O2_CHK_ANY;
+       curpx->options2 |= PR_O2_TCPCHK_CHK;
+
+       free_tcpcheck_vars(&rules->preset_vars);
+       rules->list  = NULL;
+       rules->flags = 0;
+
+       cur_arg += 2;
+       if (*args[cur_arg] && *args[cur_arg+1] &&
+           (strcmp(args[cur_arg], "EHLO") == 0 || strcmp(args[cur_arg], "HELO") == 0)) {
+               cmd = calloc(strlen(args[cur_arg]) + strlen(args[cur_arg+1]) + 1, sizeof(*cmd));
+               if (cmd)
+                       sprintf(cmd, "%s %s", args[cur_arg], args[cur_arg+1]);
+       }
+       else {
+               /* this just hits the default for now, but you could potentially expand it to allow for other stuff
+                  though, it's unlikely you'd want to send anything other than an EHLO or HELO */
+               cmd = strdup("HELO localhost");
+       }
+
+       var = tcpcheck_var_create("check.smtp_cmd");
+       if (cmd == NULL || var == NULL) {
+               ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+               goto error;
+       }
+       var->data.type = SMP_T_STR;
+       var->data.u.str.area = cmd;
+       var->data.u.str.data = strlen(cmd);
+       LIST_INIT(&var->list);
+       LIST_ADDQ(&rules->preset_vars, &var->list);
+       cmd = NULL;
+       var = NULL;
+
+       rs = tcpcheck_ruleset_lookup("*smtp-check");
+       if (rs)
+               goto ruleset_found;
+
+       rs = tcpcheck_ruleset_create("*smtp-check");
+       if (rs == NULL) {
+               ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+               goto error;
+       }
+
+       chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""},
+                                    1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->index = 0;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^[0-9]{3}[ \r]",
+                                              "min-recv", "4",
+                                              "error-status", "L7RSP",
+                                              "on-error", "%[check.payload(),cut_crlf]",
+                                              ""},
+                                   1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->index = 1;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[ \r]",
+                                              "min-recv", "4",
+                                              "error-status", "L7STS",
+                                              "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
+                                              "status-code", "check.payload(0,3)",
+                                              ""},
+                                   1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->index = 2;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       chk = parse_tcpcheck_send((char *[]){"tcp-check", "send", smtp_req, "log-format", ""},
+                                 1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->index = 3;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rstring", "^2[0-9]{2}[- \r]",
+                                              "min-recv", "4",
+                                              "error-status", "L7STS",
+                                              "on-error", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
+                                              "on-success", "%[check.payload(4,0),ltrim(' '),cut_crlf]",
+                                              "status-code", "check.payload(0,3)",
+                                              ""},
+                                   1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->index = 4;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       LIST_ADDQ(&tcpchecks_list, &rs->list);
+
+  ruleset_found:
+       rules->list = &rs->rules;
+       rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_SMTP_CHK);
+
+  out:
+       free(errmsg);
+       return err_code;
+
+  error:
+       free(cmd);
+       free(var);
+       free_tcpcheck_vars(&rules->preset_vars);
+       tcpcheck_ruleset_release(rs);
+       err_code |= ERR_ALERT | ERR_FATAL;
+       goto out;
+}
 
 static struct cfg_kw_list cfg_kws = {ILH, {
         { CFG_LISTEN, "tcp-check",  proxy_parse_tcpcheck },