Hello Willy.
On 3 March 2010 20:31, Willy Tarreau <[email protected]> wrote:
> OK that's perfect then. If you don't manage to sort out your issue
> with small packets, do not hesitate to post your work in progress
> to the list, it often helps a lot to work iteratively.
The small-packet problem turned out to be a testing fault - the apache
servers I'm using for testing were not sending the whole page when I
had set a small MTU.
Attached is a patch against v1.4.1. It contains the updated ECV patch,
and the hacks to work around check responses that span multiple
packets.
It seems to be working fine in my tests, and handles check responses
that are bigger than the buffer.
I'd be grateful for anyone's comments and suggestions.
Nick.
--
Nick Chalk.
Loadbalancer.org Ltd.
Phone: +44 (0)870 443 8779
http://www.loadbalancer.org/
diff -ur haproxy-1.4.1/include/types/proxy.h haproxy-1.4.1-ecv/include/types/proxy.h
--- haproxy-1.4.1/include/types/proxy.h 2010-03-04 22:39:19.000000000 +0000
+++ haproxy-1.4.1-ecv/include/types/proxy.h 2010-03-08 10:38:22.000000000 +0000
@@ -137,6 +137,8 @@
#define PR_O2_MYSQL_CHK 0x00020000 /* use MYSQL check for server health */
#define PR_O2_USE_PXHDR 0x00040000 /* use Proxy-Connection for proxy requests */
#define PR_O2_CHK_SNDST 0x00080000 /* send the state of each server along with HTTP health checks */
+#define PR_O2_EXPECT 0x00100000 /* http-check expect sth */
+#define PR_O2_NOEXPECT 0x00200000 /* http-check expect ! sth */
/* end of proxy->options2 */
/* bits for sticking rules */
@@ -274,6 +276,9 @@
int grace; /* grace time after stop request */
char *check_req; /* HTTP or SSL request to use for PR_O_HTTP_CHK|PR_O_SSL3_CHK */
int check_len; /* Length of the HTTP or SSL3 request */
+ char *expect_str; /* http-check expected content */
+ regex_t *expect_regex; /* http-check expected content */
+ char *expect_type; /* type of http-check, such as status, string */
struct chunk errmsg[HTTP_ERR_SIZE]; /* default or customized error messages for known errors */
int uuid; /* universally unique proxy ID, used for SNMP */
unsigned int backlog; /* force the frontend's listen backlog */
diff -ur haproxy-1.4.1/include/types/server.h haproxy-1.4.1-ecv/include/types/server.h
--- haproxy-1.4.1/include/types/server.h 2010-03-04 22:39:19.000000000 +0000
+++ haproxy-1.4.1-ecv/include/types/server.h 2010-03-08 10:38:22.000000000 +0000
@@ -147,6 +147,9 @@
struct freq_ctr sess_per_sec; /* sessions per second on this server */
int puid; /* proxy-unique server ID, used for SNMP */
+ char *check_data; /* storage of partial check results */
+ int check_data_len; /* length of partial check results stored in check_data */
+
struct {
const char *file; /* file where the section appears */
int line; /* line where the section appears */
diff -ur haproxy-1.4.1/src/cfgparse.c haproxy-1.4.1-ecv/src/cfgparse.c
--- haproxy-1.4.1/src/cfgparse.c 2010-03-04 22:39:19.000000000 +0000
+++ haproxy-1.4.1-ecv/src/cfgparse.c 2010-03-08 10:38:22.000000000 +0000
@@ -2874,8 +2874,65 @@
/* enable emission of the apparent state of a server in HTTP checks */
curproxy->options2 |= PR_O2_CHK_SNDST;
}
+ else if (strcmp(args[1], "expect") == 0) {
+ if (strcmp(args[2], "status") == 0 || strcmp(args[2], "string") == 0) {
+ curproxy->options2 |= PR_O2_EXPECT;
+ if (*(args[3]) == 0) {
+ Alert("parsing [%s:%d] : '%s %s %s' expects <regex> as an argument.\n",
+ file, linenum, args[0], args[1], args[2]);
+ return -1;
+ }
+ curproxy->expect_type = strdup(args[2]);
+ curproxy->expect_str = strdup(args[3]);
+ }
+ else if (strcmp(args[2], "rstatus") == 0 || strcmp(args[2], "rstring") == 0) {
+ curproxy->options2 |= PR_O2_EXPECT;
+ if (*(args[3]) == 0) {
+ Alert("parsing [%s:%d] : '%s %s %s' expects <regex> as an argument.\n",
+ file, linenum, args[0], args[1], args[2]);
+ return -1;
+ }
+ curproxy->expect_regex = calloc(1, sizeof(regex_t));
+ if (regcomp(curproxy->expect_regex, args[3], REG_EXTENDED) != 0) {
+ Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[0]);
+ return -1;
+ }
+ curproxy->expect_type = strdup(args[2]);
+ }
+ else if (strcmp(args[2], "!") == 0 ) {
+ curproxy->options2 |= PR_O2_NOEXPECT;
+ if (strcmp(args[3], "status") == 0 || strcmp(args[3], "string") == 0) {
+ if (*(args[4]) == 0) {
+ Alert("parsing [%s:%d] : '%s %s %s %s' expects <regex> as an argument.\n",
+ file, linenum, args[0], args[1], args[2], args[3]);
+ return -1;
+ }
+ curproxy->expect_type = strdup(args[3]);
+ curproxy->expect_str = strdup(args[4]);
+ }
+ else if (strcmp(args[3], "rstatus") == 0 || strcmp(args[3], "rstring") == 0) {
+ if (*(args[4]) == 0) {
+ Alert("parsing [%s:%d] : '%s %s %s %s' expects <regex> as an argument.\n",
+ file, linenum, args[0], args[1], args[2], args[3]);
+ return -1;
+ }
+
+ free(curproxy->expect_regex);
+ curproxy->expect_regex = calloc(1, sizeof(regex_t));
+ if (regcomp(curproxy->expect_regex, args[4], REG_EXTENDED) != 0) {
+ Alert("parsing [%s:%d] : bad regular expression '%s'.\n", file, linenum, args[0]);
+ return -1;
+ }
+ curproxy->expect_type = strdup(args[3]);
+ }
+ }
+ else {
+ Alert("parsing [%s:%d] : '%s %s' only supports (!) (r)status|(r)string'.\n", file, linenum, args[0], args[1]);
+ return -1;
+ }
+ }
else {
- Alert("parsing [%s:%d] : '%s' only supports 'disable-on-404'.\n", file, linenum, args[0]);
+ Alert("parsing [%s:%d] : '%s' only supports 'disable-on-404', 'expect' .\n", file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
@@ -3116,6 +3173,16 @@
newsrv->curfd = -1; /* no health-check in progress */
newsrv->health = newsrv->rise; /* up, but will fall down at first failure */
+ /* Allocate buffer for partial check results... */
+ if ((newsrv->check_data = calloc(BUFSIZE, sizeof(char))) == NULL) {
+ Alert("parsing [%s:%d] : out of memory while allocating check buffer.\n", file, linenum);
+ err_code |= ERR_ALERT | ERR_ABORT;
+ goto out;
+ }
+ else {
+ newsrv->check_data_len = 0;
+ }
+
cur_arg = 3;
} else {
newsrv = &curproxy->defsrv;
diff -ur haproxy-1.4.1/src/checks.c haproxy-1.4.1-ecv/src/checks.c
--- haproxy-1.4.1/src/checks.c 2010-03-04 22:39:19.000000000 +0000
+++ haproxy-1.4.1-ecv/src/checks.c 2010-03-08 12:18:04.000000000 +0000
@@ -11,6 +11,7 @@
*
*/
+
#include <assert.h>
#include <ctype.h>
#include <errno.h>
@@ -47,6 +48,8 @@
#include <proto/server.h>
#include <proto/task.h>
+static void httpchk_expect(struct server *s);
+
const struct check_status check_statuses[HCHK_STATUS_SIZE] = {
[HCHK_STATUS_UNKNOWN] = { SRV_CHK_UNKNOWN, "UNK", "Unknown" },
[HCHK_STATUS_INI] = { SRV_CHK_UNKNOWN, "INI", "Initializing" },
@@ -288,6 +291,7 @@
}
}
+
/* sends a log message when a backend goes down, and also sets last
* change date.
*/
@@ -775,6 +779,16 @@
if (s->proxy->timeout.check)
t->expire = tick_add_ifset(now_ms, s->proxy->timeout.check);
EV_FD_SET(fd, DIR_RD); /* prepare for reading reply */
+
+ /* Close the transmit channel, leaving the connection half-open... */
+ int shut = shutdown(fd, SHUT_WR);
+ int err = errno;
+
+ if (shut == -1)
+ {
+ Alert("event_srv_chk_w(): err = %i, %s\n", err, strerror(err));
+ }
+
goto out_nowake;
}
else if (ret == 0 || errno == EAGAIN)
@@ -865,6 +879,8 @@
struct task *t = fdtab[fd].owner;
struct server *s = t->context;
char *desc;
+ char *buffer;
+ int buffer_remaining;
len = -1;
@@ -882,8 +898,20 @@
* but the connection was closed on the remote end. Fortunately, recv still
* works correctly and we don't need to do the getsockopt() on linux.
*/
- len = recv(fd, trash, sizeof(trash), 0);
- if (unlikely(len < 0)) {
+
+ /* Set buffer to point to the end of the data already read, and check that
+ * there is free space remaining. If the buffer is full, proceed with
+ * running the checks without attempting another socket read.
+ */
+ buffer = s->check_data + s->check_data_len;
+ buffer_remaining = BUFSIZE - s->check_data_len;
+
+ if (buffer_remaining > 0)
+ len = recv(fd, buffer, buffer_remaining, 0);
+ else
+ len = 0;
+
+ if (len < 0) { /* recv returned an error */
if (errno == EAGAIN) {
/* not ready, we want to poll first */
fdtab[fd].ev &= ~FD_POLL_IN;
@@ -894,46 +922,64 @@
set_server_check_status(s, HCHK_STATUS_SOCKERR, NULL);
goto out_wakeup;
}
+ else if (len != 0) /* recv hasn't seen end of connection */
+ {
+ s->check_data_len += len;
+ fdtab[fd].ev &= ~FD_POLL_IN;
+ return 0;
+ }
- if (len < sizeof(trash))
- trash[len] = '\0';
+ /* Full response received.
+ * Terminate string in check_data buffer... */
+ if (s->check_data_len < BUFSIZE)
+ *buffer = '\0';
else
- trash[len-1] = '\0';
+ *(buffer - 1) = '\0';
+
+ len = s->check_data_len;
- /* Note: the response will only be accepted if read at once */
+ /* Run the checks... */
if (s->proxy->options & PR_O_HTTP_CHK) {
/* Check if the server speaks HTTP 1.X */
if ((len < strlen("HTTP/1.0 000\r")) ||
- (memcmp(trash, "HTTP/1.", 7) != 0 ||
- (trash[12] != ' ' && trash[12] != '\r')) ||
- !isdigit((unsigned char)trash[9]) || !isdigit((unsigned char)trash[10]) ||
- !isdigit((unsigned char)trash[11])) {
-
- cut_crlf(trash);
- set_server_check_status(s, HCHK_STATUS_L7RSP, trash);
+ (memcmp(s->check_data, "HTTP/1.", 7) != 0 ||
+ (*(s->check_data + 12) != ' ' && *(s->check_data + 12) != '\r')) ||
+ !isdigit(*(s->check_data + 9)) || !isdigit(*(s->check_data + 10)) ||
+ !isdigit(*(s->check_data + 11))) {
+ cut_crlf(s->check_data);
+ set_server_check_status(s, HCHK_STATUS_L7RSP, s->check_data);
goto out_wakeup;
}
- s->check_code = str2uic(&trash[9]);
-
- desc = ltrim(&trash[12], ' ');
- cut_crlf(desc);
+ s->check_code = str2uic(s->check_data + 9);
+ desc = ltrim(s->check_data + 12, ' ');
+
+ if ((s->proxy->options2 & PR_O2_EXPECT) || (s->proxy->options2 & PR_O2_NOEXPECT)) {
+ /* Run content verification check... */
+ httpchk_expect(s);
+ }
/* check the reply : HTTP/1.X 2xx and 3xx are OK */
- if (trash[9] == '2' || trash[9] == '3')
+ else if (*(s->check_data + 9) == '2' || *(s->check_data + 9) == '3') {
+ cut_crlf(desc);
set_server_check_status(s, HCHK_STATUS_L7OKD, desc);
+ }
else if ((s->proxy->options & PR_O_DISABLE404) &&
(s->state & SRV_RUNNING) &&
- (s->check_code == 404))
- /* 404 may be accepted as "stopping" only if the server was up */
+ (s->check_code == 404)) {
+ /* 404 may be accepted as "stopping" only if the server was up */
+ cut_crlf(desc);
set_server_check_status(s, HCHK_STATUS_L7OKCD, desc);
- else
+ }
+ else {
+ cut_crlf(desc);
set_server_check_status(s, HCHK_STATUS_L7STS, desc);
+ }
}
else if (s->proxy->options & PR_O_SSL3_CHK) {
/* Check for SSLv3 alert or handshake */
- if ((len >= 5) && (trash[0] == 0x15 || trash[0] == 0x16))
+ if ((len >= 5) && (*s->check_data == 0x15 || *s->check_data == 0x16))
set_server_check_status(s, HCHK_STATUS_L6OK, NULL);
else
set_server_check_status(s, HCHK_STATUS_L6RSP, NULL);
@@ -941,23 +987,22 @@
else if (s->proxy->options & PR_O_SMTP_CHK) {
/* Check if the server speaks SMTP */
if ((len < strlen("000\r")) ||
- (trash[3] != ' ' && trash[3] != '\r') ||
- !isdigit((unsigned char)trash[0]) || !isdigit((unsigned char)trash[1]) ||
- !isdigit((unsigned char)trash[2])) {
-
- cut_crlf(trash);
- set_server_check_status(s, HCHK_STATUS_L7RSP, trash);
+ (*(s->check_data + 3) != ' ' && *(s->check_data + 3) != '\r') ||
+ !isdigit(*s->check_data) || !isdigit(*(s->check_data + 1)) ||
+ !isdigit(*(s->check_data + 2))) {
+ cut_crlf(s->check_data);
+ set_server_check_status(s, HCHK_STATUS_L7RSP, s->check_data);
goto out_wakeup;
}
- s->check_code = str2uic(&trash[0]);
+ s->check_code = str2uic(s->check_data);
- desc = ltrim(&trash[3], ' ');
+ desc = ltrim(s->check_data + 3, ' ');
cut_crlf(desc);
/* Check for SMTP code 2xx (should be 250) */
- if (trash[0] == '2')
+ if (*s->check_data == '2')
set_server_check_status(s, HCHK_STATUS_L7OKD, desc);
else
set_server_check_status(s, HCHK_STATUS_L7STS, desc);
@@ -965,27 +1010,27 @@
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 (trash[4] != '\xff') {
+ 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 (len > 51) {
- desc = ltrim(&trash[5], ' ');
+ desc = ltrim(s->check_data + 5, ' ');
set_server_check_status(s, HCHK_STATUS_L7OKD, desc);
}
else {
/* 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, trash);
+ set_server_check_status(s, HCHK_STATUS_L7RSP, s->check_data);
}
}
else {
/* An error message is attached in the Error packet,
* so we can display it ! :)
*/
- desc = ltrim(&trash[7], ' ');
+ desc = ltrim(s->check_data + 7, ' ');
set_server_check_status(s, HCHK_STATUS_L7STS, desc);
}
}
@@ -998,6 +1043,10 @@
if (s->result & SRV_CHK_ERROR)
fdtab[fd].state = FD_STERROR;
+ /* Reset the check buffer... */
+ *s->check_data = '\0';
+ s->check_data_len = 0;
+
EV_FD_CLR(fd, DIR_RD);
task_wakeup(t, TASK_WOKEN_IO);
fdtab[fd].ev &= ~FD_POLL_IN;
@@ -1017,7 +1066,7 @@
int rv;
//fprintf(stderr, "process_chk: task=%p\n", t);
-
+
new_chk:
if (attempts++ > 0) {
/* we always fail to create a server, let's stop insisting... */
@@ -1416,6 +1465,115 @@
return 0;
}
+
+
+/*
+ * Perform content verification check on data in s->check_data buffer.
+ * Sets server status appropriately.
+ */
+static void httpchk_expect(struct server *s)
+{
+ int inv = 0;
+ int ret;
+ if (s->proxy->options2 & PR_O2_EXPECT)
+ inv = 1;
+
+ if (strcmp(s->proxy->expect_type, "status") == 0 ||
+ strcmp(s->proxy->expect_type, "rstatus") == 0 ) {
+ char status_code[] = "000";
+ memcpy(status_code, s->check_data + 9, 3);
+ if (strcmp(s->proxy->expect_type, "status") == 0 )
+ ret = strncmp(s->proxy->expect_str, status_code, 3);
+ else
+ ret = regexec(s->proxy->expect_regex, status_code,
+ MAX_MATCH, pmatch, 0);
+
+ #define DESC_LENGTH 37
+ char *desc = calloc(DESC_LENGTH, sizeof(char));
+ snprintf(desc, DESC_LENGTH, "HTTP status check returned "
+ "code %.3s", status_code);
+
+ if (ret == 0) {
+ if (inv)
+ set_server_check_status(s, HCHK_STATUS_L7OKD,
+ desc);
+ else
+ set_server_check_status(s, HCHK_STATUS_L7STS,
+ desc);
+ }
+ else {
+ if (inv)
+ set_server_check_status(s, HCHK_STATUS_L7STS,
+ desc);
+ else
+ set_server_check_status(s, HCHK_STATUS_L7OKD,
+ desc);
+ }
+
+ free(desc);
+ }
+ else if (strcmp(s->proxy->expect_type, "string") == 0 ||
+ strcmp(s->proxy->expect_type, "rstring") == 0) {
+ int contentlen;
+ char *contentptr = strstr(s->check_data, "\r\n\r\n");
+
+ /* Check that response contains a body... */
+ if (contentptr == NULL) {
+ set_server_check_status(s, HCHK_STATUS_L7RSP,
+ "HTTP content check could not find "
+ "response body");
+ return;
+ }
+
+ /* Step over the header separator... */
+ contentptr += 4;
+
+ /* Check that response body is not empty... */
+ if (*contentptr == '\0') {
+ set_server_check_status(s, HCHK_STATUS_L7RSP,
+ "HTTP content check found empty "
+ "response body");
+ return;
+ }
+ else {
+ contentlen = strlen(contentptr);
+ }
+
+ /* Check the response content against the supplied string
+ * or regex... */
+ if (strcmp(s->proxy->expect_type, "string") == 0) {
+ ret = strncmp(s->proxy->expect_str, contentptr,
+ contentlen - 1);
+ }
+ else {
+ ret = regexec(s->proxy->expect_regex, contentptr,
+ MAX_MATCH, pmatch, 0);
+ }
+
+ /* Set server status... */
+ if (ret == 0) {
+ if (inv)
+ set_server_check_status(s, HCHK_STATUS_L7OKD,
+ "HTTP content check matched");
+ else
+ set_server_check_status(s, HCHK_STATUS_L7RSP,
+ "HTTP content check did "
+ "not match");
+ }
+ else {
+ if (inv)
+ set_server_check_status(s, HCHK_STATUS_L7RSP,
+ "HTTP content check did "
+ "not match");
+ else
+ set_server_check_status(s, HCHK_STATUS_L7OKD,
+ "HTTP content check matched");
+ }
+ }
+}
+
+
+
/*
* Local variables:
* c-indent-level: 8
diff -ur haproxy-1.4.1/src/haproxy.c haproxy-1.4.1-ecv/src/haproxy.c
--- haproxy-1.4.1/src/haproxy.c 2010-03-04 22:39:19.000000000 +0000
+++ haproxy-1.4.1-ecv/src/haproxy.c 2010-03-08 10:58:06.000000000 +0000
@@ -854,6 +854,7 @@
free(s->id);
free(s->cookie);
+ free(s->check_data);
free(s);
s = s_next;
}/* end while(s) */