]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: checks: Implement MySQL check using tcp-check rules
authorChristopher Faulet <cfaulet@haproxy.com>
Thu, 2 Apr 2020 16:07:37 +0000 (18:07 +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 MySQL checks. This way no
extra memory is used if several backends use a MySQL check.

One for the following sequence is used :

    ## If no extra params are set
    tcp-check connect default linger
    tcp-check expect custom  ## will test the initial handshake

    ## If the username is defined
    tcp-check connect default linger
    tcp-check send-binary MYSQL_REQ log-format
    tcp-check expect custom  ## will test the initial handshake
    tcp-check expect custom  ## will test the reply to the client message

The log-format hexa string MYSQL_REQ depends on 2 preset variables, the packet
header containing the packet length and the sequence ID (check.header) and the
username (check.username). If is also different if the "post-41" option is set
or not. Expect rules relies on custom functions to check MySQL server packets.

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

index bb275c8de205b7794971335b1ab87232605400cd..322f9ddf36e441384df381822e02a4920ddd57cc 100644 (file)
@@ -77,6 +77,8 @@ int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struc
                        const char *file, int line);
 int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
                                const char *file, int line);
+int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
+                           const char *file, int line);
 
 #endif /* _PROTO_CHECKS_H */
 
index 9b34cbe45505b65e9ba4a30257ca0d4f34797759..5b3fda4adc0743321d4c8af81857b7d2f71956d2 100644 (file)
@@ -315,6 +315,7 @@ struct tcpcheck_rule {
 #define TCPCHK_RULES_PGSQL_CHK   0x00000010
 #define TCPCHK_RULES_REDIS_CHK   0x00000020
 #define TCPCHK_RULES_SMTP_CHK    0x00000030
+#define TCPCHK_RULES_MYSQL_CHK   0x00000050
 #define TCPCHK_RULES_SSL3_CHK    0x00000070
 
 /* A list of tcp-check vars, to be registered before executing a ruleset */
index be5b5c9d20e5ac1b0ab989e317ce842bcc1f5f84..6e840373705f31194716d641c43ef112499180b4 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) */
 /* unused: 0x10000000..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 */
+/* unused 0x50000000 */
 #define PR_O2_LDAP_CHK  0x60000000      /* use LDAP check for server health */
 /* unused: 0x70000000 */
 #define PR_O2_LB_AGENT_CHK 0x80000000   /* use a TCP connection to obtain a metric of server health */
index e0b1bc30d658ae847a1b869823737454fd91b5a4..b731a29119c24e8148a7edb8926876a3763b1430 100644 (file)
@@ -2416,123 +2416,10 @@ stats_error_parsing:
                        if (err_code & ERR_FATAL)
                                goto out;
                }
-
                else if (!strcmp(args[1], "mysql-check")) {
-                       /* use MYSQL request to check servers' health */
-                       if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[1], NULL))
-                               err_code |= ERR_WARN;
-
-                       free(curproxy->check_req);
-                       curproxy->check_req = NULL;
-                       curproxy->options2 &= ~PR_O2_CHK_ANY;
-                       curproxy->options2 |= PR_O2_MYSQL_CHK;
-
-                       /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
-                        * const char mysql40_client_auth_pkt[] = {
-                        *      "\x0e\x00\x00"  // packet length
-                        *      "\x01"          // packet number
-                        *      "\x00\x00"      // client capabilities
-                        *      "\x00\x00\x01"  // max packet
-                        *      "haproxy\x00"   // username (null terminated string)
-                        *      "\x00"          // filler (always 0x00)
-                        *      "\x01\x00\x00"  // packet length
-                        *      "\x00"          // packet number
-                        *      "\x01"          // COM_QUIT command
-                        * };
-                        */
-
-                       /* This is an example of a MySQL >=4.1  client Authentication packet provided by Nenad Merdanovic.
-                        * const char mysql41_client_auth_pkt[] = {
-                        *      "\x0e\x00\x00\"         // packet length
-                        *      "\x01"                  // packet number
-                        *      "\x00\x00\x00\x00"      // client capabilities
-                        *      "\x00\x00\x00\x01"      // max packet
-                        *      "\x21"                  // character set (UTF-8)
-                        *      char[23]                // All zeroes
-                        *      "haproxy\x00"           // username (null terminated string)
-                        *      "\x00"                  // filler (always 0x00)
-                        *      "\x01\x00\x00"          // packet length
-                        *      "\x00"                  // packet number
-                        *      "\x01"                  // COM_QUIT command
-                        * };
-                        */
-
-
-                       if (*(args[2])) {
-                               int cur_arg = 2;
-
-                               while (*(args[cur_arg])) {
-                                       if (strcmp(args[cur_arg], "user") == 0) {
-                                               char *mysqluser;
-                                               int packetlen, reqlen, userlen;
-
-                                               /* suboption header - needs additional argument for it */
-                                               if (*(args[cur_arg+1]) == 0) {
-                                                       ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
-                                                                file, linenum, args[0], args[1], args[cur_arg]);
-                                                       err_code |= ERR_ALERT | ERR_FATAL;
-                                                       goto out;
-                                               }
-                                               mysqluser = args[cur_arg + 1];
-                                               userlen   = strlen(mysqluser);
-
-                                               if (*(args[cur_arg+2])) {
-                                                       if (!strcmp(args[cur_arg+2], "post-41")) {
-                                                               packetlen = userlen + 7 + 27;
-                                                               reqlen    = packetlen + 9;
-
-                                                               free(curproxy->check_req);
-                                                               curproxy->check_req = calloc(1, reqlen);
-                                                               curproxy->check_len = reqlen;
-
-                                                               snprintf(curproxy->check_req, 4, "%c%c%c",
-                                                                       ((unsigned char) packetlen & 0xff),
-                                                                       ((unsigned char) (packetlen >> 8) & 0xff),
-                                                                       ((unsigned char) (packetlen >> 16) & 0xff));
-
-                                                               curproxy->check_req[3] = 1;
-                                                               curproxy->check_req[5] = 0x82; // 130
-                                                               curproxy->check_req[11] = 1;
-                                                               curproxy->check_req[12] = 33;
-                                                               memcpy(&curproxy->check_req[36], mysqluser, userlen);
-                                                               curproxy->check_req[36 + userlen + 1 + 1]     = 1;
-                                                               curproxy->check_req[36 + userlen + 1 + 1 + 4] = 1;
-                                                               cur_arg += 3;
-                                                       } else {
-                                                               ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41'.\n", file, linenum, args[cur_arg+2]);
-                                                               err_code |= ERR_ALERT | ERR_FATAL;
-                                                               goto out;
-                                                       }
-                                               } else {
-                                                       packetlen = userlen + 7;
-                                                       reqlen    = packetlen + 9;
-
-                                                       free(curproxy->check_req);
-                                                       curproxy->check_req = calloc(1, reqlen);
-                                                       curproxy->check_len = reqlen;
-
-                                                       snprintf(curproxy->check_req, 4, "%c%c%c",
-                                                               ((unsigned char) packetlen & 0xff),
-                                                               ((unsigned char) (packetlen >> 8) & 0xff),
-                                                               ((unsigned char) (packetlen >> 16) & 0xff));
-
-                                                       curproxy->check_req[3] = 1;
-                                                       curproxy->check_req[5] = 0x80;
-                                                       curproxy->check_req[8] = 1;
-                                                       memcpy(&curproxy->check_req[9], mysqluser, userlen);
-                                                       curproxy->check_req[9 + userlen + 1 + 1]     = 1;
-                                                       curproxy->check_req[9 + userlen + 1 + 1 + 4] = 1;
-                                                       cur_arg += 2;
-                                               }
-                                       } else {
-                                               /* unknown suboption - catchall */
-                                               ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n",
-                                                        file, linenum, args[0], args[1]);
-                                               err_code |= ERR_ALERT | ERR_FATAL;
-                                               goto out;
-                                       }
-                               } /* end while loop */
-                       }
+                       err_code |= proxy_parse_mysql_check_opt(args, 0, curproxy, &defproxy, file, linenum);
+                       if (err_code & ERR_FATAL)
+                               goto out;
                }
                else if (!strcmp(args[1], "ldap-check")) {
                        /* use LDAP request to check servers' health */
index d0452a9f8ac1a9caa3583a07e2655417c294c56f..0fcf91592b4aa02cd10077358eff65d85f8d13d2 100644 (file)
@@ -1156,100 +1156,6 @@ static void __event_srv_chk_r(struct conn_stream *cs)
                break;
        }
 
-       case PR_O2_MYSQL_CHK:
-               if (!done && b_data(&check->bi) < 5)
-                       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;
-
-               if (s->proxy->check_len == 0) { // old mode
-                       if (*(b_head(&check->bi) + 4) != '\xff') {
-                               /* We set the MySQL Version in description for information purpose
-                                * FIXME : it can be cool to use MySQL Version for other purpose,
-                                * like mark as down old MySQL server.
-                                */
-                               if (b_data(&check->bi) > 51) {
-                                       desc = ltrim(b_head(&check->bi) + 5, ' ');
-                                       set_server_check_status(check, HCHK_STATUS_L7OKD, desc);
-                               }
-                               else {
-                                       if (!done)
-                                               goto wait_more_data;
-
-                                       /* it seems we have a OK packet but without a valid length,
-                                        * it must be a protocol error
-                                        */
-                                       set_server_check_status(check, HCHK_STATUS_L7RSP, b_head(&check->bi));
-                               }
-                       }
-                       else {
-                               /* An error message is attached in the Error packet */
-                               desc = ltrim(b_head(&check->bi) + 7, ' ');
-                               set_server_check_status(check, HCHK_STATUS_L7STS, desc);
-                       }
-               } else {
-                       unsigned int first_packet_len = ((unsigned int) *b_head(&check->bi)) +
-                                                       (((unsigned int) *(b_head(&check->bi) + 1)) << 8) +
-                                                       (((unsigned int) *(b_head(&check->bi) + 2)) << 16);
-
-                       if (b_data(&check->bi) == first_packet_len + 4) {
-                               /* MySQL Error packet always begin with field_count = 0xff */
-                               if (*(b_head(&check->bi) + 4) != '\xff') {
-                                       /* We have only one MySQL packet and it is a Handshake Initialization packet
-                                       * but we need to have a second packet to know if it is alright
-                                       */
-                                       if (!done && b_data(&check->bi) < first_packet_len + 5)
-                                               goto wait_more_data;
-                               }
-                               else {
-                                       /* We have only one packet and it is an Error packet,
-                                       * an error message is attached, so we can display it
-                                       */
-                                       desc = &b_head(&check->bi)[7];
-                                       //ha_warning("onlyoneERR: %s\n", desc);
-                                       set_server_check_status(check, HCHK_STATUS_L7STS, desc);
-                               }
-                       } else if (b_data(&check->bi) > first_packet_len + 4) {
-                               unsigned int second_packet_len = ((unsigned int) *(b_head(&check->bi) + first_packet_len + 4)) +
-                                                                (((unsigned int) *(b_head(&check->bi) + first_packet_len + 5)) << 8) +
-                                                                (((unsigned int) *(b_head(&check->bi) + first_packet_len + 6)) << 16);
-
-                               if (b_data(&check->bi) == first_packet_len + 4 + second_packet_len + 4 ) {
-                                       /* We have 2 packets and that's good */
-                                       /* Check if the second packet is a MySQL Error packet or not */
-                                       if (*(b_head(&check->bi) + first_packet_len + 8) != '\xff') {
-                                               /* No error packet */
-                                               /* We set the MySQL Version in description for information purpose */
-                                               desc = &b_head(&check->bi)[5];
-                                               //ha_warning("2packetOK: %s\n", desc);
-                                               set_server_check_status(check, HCHK_STATUS_L7OKD, desc);
-                                       }
-                                       else {
-                                               /* An error message is attached in the Error packet
-                                               * so we can display it ! :)
-                                               */
-                                               desc = &b_head(&check->bi)[first_packet_len+11];
-                                               //ha_warning("2packetERR: %s\n", desc);
-                                               set_server_check_status(check, HCHK_STATUS_L7STS, desc);
-                                       }
-                               }
-                       }
-                       else {
-                               if (!done)
-                                       goto wait_more_data;
-
-                               /* it seems we have a Handshake Initialization packet but without a valid length,
-                                * it must be a protocol error
-                                */
-                               desc = &b_head(&check->bi)[5];
-                               //ha_warning("protoerr: %s\n", desc);
-                               set_server_check_status(check, HCHK_STATUS_L7RSP, desc);
-                       }
-               }
-               break;
-
        case PR_O2_LDAP_CHK:
                if (!done && b_data(&check->bi) < 14)
                        goto wait_more_data;
@@ -2776,6 +2682,95 @@ static void tcpcheck_onsuccess_message(struct buffer *msg, struct check *check,
        *(b_tail(msg)) = '\0';
 }
 
+static enum tcpcheck_eval_ret tcpcheck_mysql_expect_packet(struct check *check, struct tcpcheck_rule *rule,
+                                                          unsigned int offset, int last_read)
+{
+       enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
+       enum healthcheck_status status;
+       struct buffer *msg = NULL;
+       struct ist desc = ist(NULL);
+       unsigned int err = 0, plen = 0;
+
+
+       /* 3 Bytes for the packet length and 1 byte for the sequence id */
+       if (!last_read && b_data(&check->bi) < offset+4) {
+               if (!last_read)
+                       goto wait_more_data;
+
+               /* invalid length or truncated response */
+               status = HCHK_STATUS_L7RSP;
+               goto error;
+       }
+
+       plen = ((unsigned char) *b_peek(&check->bi, offset)) +
+               (((unsigned char) *(b_peek(&check->bi, offset+1))) << 8) +
+               (((unsigned char) *(b_peek(&check->bi, offset+2))) << 16);
+
+       if (b_data(&check->bi) < offset+plen+4) {
+               if (!last_read)
+                       goto wait_more_data;
+
+               /* invalid length or truncated response */
+               status = HCHK_STATUS_L7RSP;
+               goto error;
+       }
+
+       if (*b_peek(&check->bi, offset+4) == '\xff') {
+               /* MySQL Error packet always begin with field_count = 0xff */
+               status = HCHK_STATUS_L7STS;
+               err = ((unsigned char) *b_peek(&check->bi, offset+5)) +
+                       (((unsigned char) *(b_peek(&check->bi, offset+6))) << 8);
+               desc = ist2(b_peek(&check->bi, offset+7), b_data(&check->bi) - offset - 7);
+               goto error;
+       }
+
+       if (get_next_tcpcheck_rule(check->tcpcheck_rules, rule) != NULL) {
+               /* Not the last rule, continue */
+               goto out;
+       }
+
+       /* We set the MySQL Version in description for information purpose
+        * FIXME : it can be cool to use MySQL Version for other purpose,
+        * like mark as down old MySQL server.
+        */
+       set_server_check_status(check, HCHK_STATUS_L7OKD, b_peek(&check->bi, 5));
+
+  out:
+       free_trash_chunk(msg);
+       return ret;
+
+  error:
+       ret = TCPCHK_EVAL_STOP;
+       check->code = err;
+       msg = alloc_trash_chunk();
+       if (msg)
+               tcpcheck_onerror_message(msg, check, rule, 0, desc);
+       set_server_check_status(check, status, (msg ? b_head(msg) : NULL));
+       goto out;
+
+  wait_more_data:
+       ret = TCPCHK_EVAL_WAIT;
+       goto out;
+}
+
+
+static enum tcpcheck_eval_ret tcpcheck_mysql_expect_iniths(struct check *check, struct tcpcheck_rule *rule, int last_read)
+{
+       return tcpcheck_mysql_expect_packet(check, rule, 0, last_read);
+}
+
+static enum tcpcheck_eval_ret tcpcheck_mysql_expect_ok(struct check *check, struct tcpcheck_rule *rule, int last_read)
+{
+       unsigned int hslen = 0;
+
+       hslen = 4 + ((unsigned char) *b_head(&check->bi)) +
+               (((unsigned char) *(b_peek(&check->bi, 1))) << 8) +
+               (((unsigned char) *(b_peek(&check->bi, 2))) << 16);
+
+       return tcpcheck_mysql_expect_packet(check, rule, hslen, last_read);
+}
+
+
 /* Evaluate a TCPCHK_ACT_CONNECT rule. It returns 1 to evaluate the next rule, 0
  * to wait and -1 to stop the check. */
 static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct tcpcheck_rule *rule)
@@ -5535,6 +5530,240 @@ int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, s
 }
 
 
+/* Parses the "option mysql-check" proxy keyword */
+int proxy_parse_mysql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx,
+                               const char *file, int line)
+{
+       /* This is an example of a MySQL >=4.0 client Authentication packet kindly provided by Cyril Bonte.
+        * const char mysql40_client_auth_pkt[] = {
+        *      "\x0e\x00\x00"  // packet length
+        *      "\x01"          // packet number
+        *      "\x00\x00"      // client capabilities
+        *      "\x00\x00\x01"  // max packet
+        *      "haproxy\x00"   // username (null terminated string)
+        *      "\x00"          // filler (always 0x00)
+        *      "\x01\x00\x00"  // packet length
+        *      "\x00"          // packet number
+        *      "\x01"          // COM_QUIT command
+        * };
+        */
+       static char mysql40_rsname[] = "*mysql40-check";
+       static char mysql40_req[] = {
+               "%[var(check.header),hex]"     /* 3 bytes for the packet length and 1 byte for the sequence ID */
+               "0080"                         /* client capabilities */
+               "000001"                       /* max packet */
+               "%[var(check.username),hex]00" /* the username */
+               "00"                           /* filler (always 0x00) */
+               "010000"                       /* packet length*/
+               "00"                           /* sequence ID */
+               "01"                           /* COM_QUIT command */
+       };
+
+       /* This is an example of a MySQL >=4.1  client Authentication packet provided by Nenad Merdanovic.
+        * const char mysql41_client_auth_pkt[] = {
+        *      "\x0e\x00\x00\"         // packet length
+        *      "\x01"                  // packet number
+        *      "\x00\x00\x00\x00"      // client capabilities
+        *      "\x00\x00\x00\x01"      // max packet
+        *      "\x21"                  // character set (UTF-8)
+        *      char[23]                // All zeroes
+        *      "haproxy\x00"           // username (null terminated string)
+        *      "\x00"                  // filler (always 0x00)
+        *      "\x01\x00\x00"          // packet length
+        *      "\x00"                  // packet number
+        *      "\x01"                  // COM_QUIT command
+        * };
+        */
+       static char mysql41_rsname[] = "*mysql41-check";
+       static char mysql41_req[] = {
+               "%[var(check.header),hex]"     /* 3 bytes for the packet length and 1 byte for the sequence ID */
+               "00820000"                     /* client capabilities */
+               "00800001"                     /* max packet */
+               "21"                           /* character set (UTF-8) */
+               "000000000000000000000000"     /* 23 bytes, al zeroes */
+               "0000000000000000000000"
+               "%[var(check.username),hex]00" /* the username */
+               "00"                           /* filler (always 0x00) */
+               "010000"                       /* packet length*/
+               "00"                           /* sequence ID */
+               "01"                           /* COM_QUIT command */
+       };
+
+       struct tcpcheck_ruleset *rs = NULL;
+       struct tcpcheck_rules *rules = &curpx->tcpcheck_rules;
+       struct tcpcheck_rule *chk;
+       struct tcpcheck_var *var = NULL;
+       char *mysql_rsname = "*mysql-check";
+       char *mysql_req = NULL, *hdr = NULL, *user = NULL, *errmsg = NULL;
+       int index = 0, 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(3, 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]) {
+               char *user;
+               int packetlen, userlen;
+
+               if (strcmp(args[cur_arg], "user") != 0) {
+                       ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user' (got '%s').\n",
+                                file, line, args[0], args[1], args[cur_arg]);
+                       goto error;
+               }
+
+               if (*(args[cur_arg+1]) == 0) {
+                       ha_alert("parsing [%s:%d] : '%s %s %s' expects <username> as argument.\n",
+                                file, line, args[0], args[1], args[cur_arg]);
+                       goto error;
+               }
+
+               hdr     = calloc(4, sizeof(*hdr));
+               user    = strdup(args[cur_arg+1]);
+               userlen = strlen(args[cur_arg+1]);
+
+               if (hdr == NULL || user == NULL) {
+                       ha_alert("parsing [%s:%d] : out of memory.\n", file, line);
+                       goto error;
+               }
+
+               if (*args[cur_arg+2]) {
+                       if (strcmp(args[cur_arg+2], "post-41") != 0) {
+                               ha_alert("parsing [%s:%d] : keyword '%s' only supports option 'post-41' (got '%s').\n",
+                                        file, line, args[cur_arg], args[cur_arg+2]);
+                               goto error;
+                       }
+                       packetlen = userlen + 7 + 27;
+                       mysql_req = mysql41_req;
+                       mysql_rsname  = mysql41_rsname;
+               }
+               else {
+                       packetlen = userlen + 7;
+                       mysql_req = mysql40_req;
+                       mysql_rsname  = mysql40_rsname;
+               }
+
+               hdr[0] = (unsigned char)(packetlen & 0xff);
+               hdr[1] = (unsigned char)((packetlen >> 8) & 0xff);
+               hdr[2] = (unsigned char)((packetlen >> 16) & 0xff);
+               hdr[3] = 1;
+
+               var = tcpcheck_var_create("check.header");
+               if (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 = hdr;
+               var->data.u.str.data = 4;
+               LIST_INIT(&var->list);
+               LIST_ADDQ(&rules->preset_vars, &var->list);
+               hdr = NULL;
+               var = NULL;
+
+               var = tcpcheck_var_create("check.username");
+               if (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 = user;
+               var->data.u.str.data = strlen(user);
+               LIST_INIT(&var->list);
+               LIST_ADDQ(&rules->preset_vars, &var->list);
+               user = NULL;
+               var = NULL;
+       }
+
+       rs = tcpcheck_ruleset_lookup(mysql_rsname);
+       if (rs)
+               goto ruleset_found;
+
+       rs = tcpcheck_ruleset_create(mysql_rsname);
+       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 = index++;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       if (mysql_req) {
+               chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", mysql_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 = index++;
+               LIST_ADDQ(&rs->rules, &chk->list);
+       }
+
+       chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
+                                   1, curpx, &rs->rules, file, line, &errmsg);
+       if (!chk) {
+               ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+               goto error;
+       }
+       chk->expect.custom = tcpcheck_mysql_expect_iniths;
+       chk->index = index++;
+       LIST_ADDQ(&rs->rules, &chk->list);
+
+       if (mysql_req) {
+               chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""},
+                                           1, curpx, &rs->rules, file, line, &errmsg);
+               if (!chk) {
+                       ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg);
+                       goto error;
+               }
+               chk->expect.custom = tcpcheck_mysql_expect_ok;
+               chk->index = index++;
+               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_MYSQL_CHK);
+
+  out:
+       free(errmsg);
+       return err_code;
+
+  error:
+       free(hdr);
+       free(user);
+       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 },
         { 0, NULL, NULL },