logging.
-option mysql-check
- Use Mysql health checks for server testing
+option mysql-check [ user <username> ]
+ Use MySQL health checks for server testing
May be used in sections : defaults | frontend | listen | backend
yes | no | yes | yes
- Arguments : none
-
- The check consists in parsing Mysql Handshake Initialisation packet or Error
- packet, which is sent by MySQL server on connect. It is a basic but useful
- test which does not produce any logging on the server. However, it does not
- check database presence nor database consistency, nor user permission to
- access. To do this, you can use an external check with xinetd for example.
+ Arguments :
+ user <username> This is the username which will be used when connecting
+ to MySQL server.
+
+ If you specify a username, the check consists of sending two MySQL packet,
+ one Client Authentication packet, and one QUIT packet, to correctly close
+ MySQL session. We then parse the MySQL Handshake Initialisation packet and/or
+ Error packet. It is a basic but useful test which does not produce error nor
+ aborted connect on the server. However, it requires adding an authorization
+ in the MySQL table, like this :
+
+ USE mysql;
+ INSERT INTO user (Host,User) values ('<ip_of_haproxy>','<username>');
+ FLUSH PRIVILEGES;
+
+ If you don't specify a username (it is deprecated and not recommended), the
+ check only consists in parsing the Mysql Handshake Initialisation packet or
+ Error packet, we don't send anything in this mode. It was reported that it
+ can generate lockout if check is too frequent and/or if there is not enough
+ traffic. In fact, you need in this case to check MySQL "max_connect_errors"
+ value as if a connection is established successfully within fewer than MySQL
+ "max_connect_errors" attempts after a previous connection was interrupted,
+ the error count for the host is cleared to zero. If HAProxy's server get
+ blocked, the "FLUSH HOSTS" statement is the only way to unblock it.
+
+ Remember that this does not check database presence nor database consistency.
+ To do this, you can use an external check with xinetd for example.
+
+ The check requires MySQL >=4.0, for older version, please use TCP check.
Most often, an incoming MySQL server needs to see the client's IP address for
various purposes, including IP privilege matching and connection logging.
}
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->options &= ~PR_O_HTTP_CHK;
curproxy->options2 &= ~PR_O2_SSL3_CHK;
curproxy->options2 &= ~PR_O2_LDAP_CHK;
curproxy->options2 |= PR_O2_MYSQL_CHK;
+
+ /* This is an exemple of an 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
+ * };
+ */
+
+ 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) {
+ 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);
+ packetlen = userlen + 7;
+ reqlen = packetlen + 9;
+
+ free(curproxy->check_req);
+ curproxy->check_req = (char *)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[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 */
+ 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 */
+ }
}
else if (!strcmp(args[1], "ldap-check")) {
/* use LDAP request to check servers' health */
/*
* This function is used only for server health-checks. It handles the server's
- * reply to an HTTP request or SSL HELLO. It calls set_server_check_status() to
- * update s->check_status, s->check_duration and s->result.
+ * reply to an HTTP request, SSL HELLO or MySQL client Auth. It calls
+ * set_server_check_status() to update s->check_status, s->check_duration
+ * and s->result.
* The set_server_check_status function is called with HCHK_STATUS_L7OKD if
* an HTTP server replies HTTP 2xx or 3xx (valid responses), if an SMTP server
set_server_check_status(s, HCHK_STATUS_L7STS, desc);
}
else if (s->proxy->options2 & PR_O2_MYSQL_CHK) {
- /* MySQL Error packet always begin with field_count = 0xff
- * contrary to OK Packet who always begin whith 0x00 */
if (!done && s->check_data_len < 5)
goto wait_more_data;
- if (*(s->check_data + 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 (s->check_data_len > 51) {
- desc = ltrim(s->check_data + 5, ' ');
- set_server_check_status(s, HCHK_STATUS_L7OKD, desc);
+ if (s->proxy->check_len == 0) { // old mode
+ if (*(s->check_data + 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 (s->check_data_len > 51) {
+ desc = ltrim(s->check_data + 5, ' ');
+ set_server_check_status(s, 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(s, HCHK_STATUS_L7RSP, s->check_data);
+ }
+ }
+ else {
+ /* An error message is attached in the Error packet */
+ desc = ltrim(s->check_data + 7, ' ');
+ set_server_check_status(s, HCHK_STATUS_L7STS, desc);
+ }
+ } else {
+ unsigned int first_packet_len = ((unsigned int) *s->check_data) +
+ (((unsigned int) *(s->check_data + 1)) << 8) +
+ (((unsigned int) *(s->check_data + 2)) << 16);
+
+ if (s->check_data_len == first_packet_len + 4) {
+ /* MySQL Error packet always begin with field_count = 0xff */
+ if (*(s->check_data + 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 && s->check_data_len < 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 = &s->check_data[7];
+ //Warning("onlyoneERR: %s\n", desc);
+ set_server_check_status(s, HCHK_STATUS_L7STS, desc);
+ }
+ } else if (s->check_data_len > first_packet_len + 4) {
+ unsigned int second_packet_len = ((unsigned int) *(s->check_data + first_packet_len + 4)) +
+ (((unsigned int) *(s->check_data + first_packet_len + 5)) << 8) +
+ (((unsigned int) *(s->check_data + first_packet_len + 6)) << 16);
+
+ if (s->check_data_len == 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 (*(s->check_data + first_packet_len + 8) != '\xff') {
+ /* No error packet */
+ /* We set the MySQL Version in description for information purpose */
+ desc = &s->check_data[5];
+ //Warning("2packetOK: %s\n", desc);
+ set_server_check_status(s, HCHK_STATUS_L7OKD, desc);
+ }
+ else {
+ /* An error message is attached in the Error packet
+ * so we can display it ! :)
+ */
+ desc = &s->check_data[first_packet_len+11];
+ //Warning("2packetERR: %s\n", desc);
+ set_server_check_status(s, HCHK_STATUS_L7STS, desc);
+ }
+ }
}
else {
if (!done)
goto wait_more_data;
- /* it seems we have a OK packet but without a valid length,
+ /* it seems we have a Handshake Initialization packet but without a valid length,
* it must be a protocol error
*/
- set_server_check_status(s, HCHK_STATUS_L7RSP, s->check_data);
+ desc = &s->check_data[5];
+ //Warning("protoerr: %s\n", desc);
+ set_server_check_status(s, HCHK_STATUS_L7RSP, desc);
}
}
- else {
- /* An error message is attached in the Error packet,
- * so we can display it ! :)
- */
- desc = ltrim(s->check_data + 7, ' ');
- set_server_check_status(s, HCHK_STATUS_L7STS, desc);
- }
}
else if (s->proxy->options2 & PR_O2_LDAP_CHK) {
if (!done && s->check_data_len < 14)
bind :3307
mode tcp
balance roundrobin
- option mysql-check
+ option mysql-check user haproxy
server srv1 127.0.0.1:3306 check port 3306 inter 1000 fall 1
# server srv2 127.0.0.2:3306 check port 3306 inter 1000 fall 1
# server srv3 127.0.0.3:3306 check port 3306 inter 1000 fall 1