Hello all,
I have updated the mysql-check with idea and code from Cyril. I send()
the authentication packet the same way as the ssl hello packet,
i think it is more cleaner than send() in the event_srv_chk_r().
I now handle a correct banner but with error after sending the
authentication packet.
Cyril, your "sizeof" oops show me that there is the same bug on
ssl-hello-chk so i made a patch for that.
Like Cyril says, it could be enhanced with a configurable username, or
other things.
Documentation is updated,
It was tested on 4.0.x, 4.1.x, and 5.x.
Feel free to test and report bugs,
Regards,
Hervé.
On 01/14/2010 11:00 PM, Cyril Bonté wrote:
Le Jeudi 14 Janvier 2010 22:20:20, Cyril Bonté a écrit :
Please find the patch in attachment if you're interested.
Oops, 2 bugs were left in that version :
1. the 8 bytes for scramble buff are not needed when there's no password
2. sizeof(MYSQL40_HANDSHAKE_ACK) gives one more byte than required.
Sorry for the previous patch.
--
Hervé COMMOWICK, EXOSEC (http://www.exosec.fr/)
ZAC des Metz - 3 Rue du petit robinson - 78350 JOUY EN JOSAS
Tel: +33 1 30 67 60 65 - Fax: +33 1 75 43 40 70
mailto:[email protected]
--- haproxy/src/cfgparse.c 2010-01-14 09:37:09.000000000 +0100
+++ haproxy.dev/src/cfgparse.c 2010-01-15 16:32:51.958747436 +0100
@@ -4728,9 +4728,9 @@
}
if (curproxy->options & PR_O_SSL3_CHK) {
- curproxy->check_len = sizeof(sslv3_client_hello_pkt);
- curproxy->check_req = (char *)malloc(sizeof(sslv3_client_hello_pkt));
- memcpy(curproxy->check_req, sslv3_client_hello_pkt, sizeof(sslv3_client_hello_pkt));
+ curproxy->check_len = sizeof(sslv3_client_hello_pkt)-1;
+ curproxy->check_req = (char *)malloc(sizeof(sslv3_client_hello_pkt)-1);
+ memcpy(curproxy->check_req, sslv3_client_hello_pkt, sizeof(sslv3_client_hello_pkt)-1);
}
/* The small pools required for the capture lists */
--- haproxy/src/checks.c 2010-01-15 12:31:49.000000000 +0100
+++ haproxy.dev/src/checks.c 2010-01-15 12:42:16.165353336 +0100
@@ -748,8 +748,9 @@
/*
* 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
@@ -865,26 +874,55 @@
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 */
+ unsigned int first_packet_len;
+ first_packet_len = ((unsigned int) trash[0]) + (((unsigned int) trash[1]) << 8) + (((unsigned int) trash[2]) << 16);
+
+ /* MySQL Error packet always begin with field_count = 0xff */
if (trash[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 (len > 51) {
+ /* Check if we have only one MySQL packet in buffer */
+ if (len == first_packet_len + 4) {
+ /* We have only one MySQL paquet
+ * and it seems to be a Handshake Initialization packet
+ * It is not normal because we must normally have 2 packet
+ * but it can be good on *real* low latency network
+ */
+
+ /* We set the MySQL Version in description for information purpose */
desc = ltrim(&trash[5], ' ');
set_server_check_status(s, HCHK_STATUS_L7OKD, desc);
}
+ else if (len > first_packet_len + 4) {
+ unsigned int second_packet_len;
+ second_packet_len = ((unsigned int) trash[first_packet_len+4]) + (((unsigned int) trash[first_packet_len+5]) << 8) + (((unsigned int) trash[first_packet_len+6]) << 16);
+ if (len == first_packet_len + 4 + second_packet_len + 4 ) {
+ /* We have 2 packet and that's good */
+ /* Check if the second packet is not a MySQL Error packet */
+ if (trash[first_packet_len+8] != '\xff') {
+ /* No error packet */
+ /* We set the MySQL Version in description for information purpose */
+ desc = ltrim(&trash[5], ' ');
+ 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 = ltrim(&trash[first_packet_len+11], ' ');
+ set_server_check_status(s, HCHK_STATUS_L7STS, desc);
+ }
+ }
+ }
else {
- /* 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, trash);
+ desc = ltrim(&trash[5], ' ');
+ set_server_check_status(s, HCHK_STATUS_L7RSP, desc);
}
}
else {
- /* An error message is attached in the Error packet,
+ /* We have only one packet, and it is an Error packet,
+ * an error message is attached,
* so we can display it ! :)
*/
desc = ltrim(&trash[7], ' ');
--- haproxy/src/cfgparse.c 2010-01-14 09:37:09.000000000 +0100
+++ haproxy.dev/src/cfgparse.c 2010-01-15 12:01:27.182252393 +0100
@@ -89,6 +89,17 @@
"\x00" /* Compression Type : 0x00 = NULL compression */
};
+/* This is a MySQL >=4.0 client Authentication packet proudly provided by Cyril Bonte
+ * it must be sent to ensure proper connection */
+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) */
+};
+
/* various keyword modifiers */
enum kw_mod {
KWM_STD = 0, /* normal */
@@ -2484,6 +2495,9 @@
}
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->options &= ~PR_O_HTTP_CHK;
curproxy->options &= ~PR_O_SSL3_CHK;
@@ -4733,6 +4747,12 @@
memcpy(curproxy->check_req, sslv3_client_hello_pkt, sizeof(sslv3_client_hello_pkt)-1);
}
+ if (curproxy->options2 & PR_O2_MYSQL_CHK) {
+ curproxy->check_len = sizeof(mysql40_client_auth_pkt)-1;
+ curproxy->check_req = (char *)malloc(sizeof(mysql40_client_auth_pkt)-1);
+ memcpy(curproxy->check_req, mysql40_client_auth_pkt, sizeof(mysql40_client_auth_pkt)-1);
+ }
+
/* The small pools required for the capture lists */
if (curproxy->nb_req_cap)
curproxy->req_cap_pool = create_pool("ptrcap",
--- haproxy/doc/configuration.txt 2010-01-14 09:37:09.000000000 +0100
+++ haproxy.dev/doc/configuration.txt 2010-01-15 16:09:02.858912373 +0100
@@ -2780,11 +2780,20 @@
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.
+ The check consists of sending a Client Authentication packet, with "haproxy"
+ username. We parse the Mysql Handshake Initialisation packet and/or
+ Error packet. It is a basic but useful test which does not produce error
+ 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>','haproxy');
+ FLUSH PRIVILEGES;
+
+ Remember that it 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.