diff --git a/include/types/proxy.h b/include/types/proxy.h index 3ac80d8..167eacf 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -139,6 +139,8 @@ #define PR_O2_CHK_SNDST 0x00080000 /* send the state of each server along with HTTP health checks */ #define PR_O2_SSL3_CHK 0x00100000 /* use SSLv3 CLIENT_HELLO packets for server health */ #define PR_O2_FAKE_KA 0x00200000 /* pretend we do keep-alive with server eventhough we close */ +#define PR_O2_EXPECT 0x00400000 /* http-check expect sth */ +#define PR_O2_NOEXPECT 0x00800000 /* http-check expect !sth */ /* end of proxy->options2 */ /* bits for sticking rules */ @@ -279,6 +281,9 @@ struct proxy { 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 --git a/src/cfgparse.c b/src/cfgparse.c index cc82544..fc9043a 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -2883,8 +2883,65 @@ stats_error_parsing: /* 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 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 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 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 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; } diff --git a/src/checks.c b/src/checks.c index d326625..4a06e0a 100644 --- a/src/checks.c +++ b/src/checks.c @@ -47,6 +47,8 @@ #include #include +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" }, @@ -939,8 +941,12 @@ static int event_srv_chk_r(int fd) 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 (*(s->check_data + 9) == '2' || *(s->check_data + 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); } @@ -1461,6 +1467,113 @@ int start_checks() { 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