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.

Reply via email to