From: Christopher Faulet Date: Thu, 2 Apr 2020 09:44:39 +0000 (+0200) Subject: MEDIUM: checks: Implement postgres check using tcp-check rules X-Git-Tag: v2.2-dev7~125 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ce355074f1fc76102753cc588d17e2530b4f31a0;p=thirdparty%2Fhaproxy.git MEDIUM: checks: Implement postgres check using tcp-check rules A shared tcp-check ruleset is now created to support postgres check. This way no extra memory is used if several backends use a pgsql check. The following sequence is used : tcp-check connect default linger tcp-check send-binary PGSQL_REQ log-format tcp-check expect !rstring "^E" min-recv 5 \ error-status "L7RSP" on-error "%[check.payload(6,0)]" tcp-check expect rbinary "^520000000800000000 min-recv "9" \ error-status "L7STS" \ on-success "PostgreSQL server is ok" \ on-error "PostgreSQL unknown error" The log-format hexa string PGSQL_REQ depends on 2 preset variables, the packet length (check.plen) and the username (check.username). --- diff --git a/include/proto/checks.h b/include/proto/checks.h index b3ea9175bf..bb275c8de2 100644 --- a/include/proto/checks.h +++ b/include/proto/checks.h @@ -74,7 +74,9 @@ int proxy_parse_redis_check_opt(char **args, int cur_arg, struct proxy *curpx, s int proxy_parse_ssl_hello_chk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx, const char *file, int line); int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx, - const char *file, int line); + const char *file, int line); +int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx, + const char *file, int line); #endif /* _PROTO_CHECKS_H */ diff --git a/include/types/checks.h b/include/types/checks.h index 541666c664..9b34cbe455 100644 --- a/include/types/checks.h +++ b/include/types/checks.h @@ -312,6 +312,7 @@ struct tcpcheck_rule { #define TCPCHK_RULES_SHARED 0x00000001 /* Set for shared list of tcp-check rules */ #define TCPCHK_RULES_DEF 0x00000002 /* Ruleset inherited from the default section */ +#define TCPCHK_RULES_PGSQL_CHK 0x00000010 #define TCPCHK_RULES_REDIS_CHK 0x00000020 #define TCPCHK_RULES_SMTP_CHK 0x00000030 #define TCPCHK_RULES_SSL3_CHK 0x00000070 diff --git a/include/types/proxy.h b/include/types/proxy.h index 0f4301d272..be5b5c9d20 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -169,9 +169,7 @@ enum PR_SRV_STATE_FILE { /* server health checks */ #define PR_O2_CHK_NONE 0x00000000 /* no L7 health checks configured (TCP by default) */ -#define PR_O2_PGSQL_CHK 0x10000000 /* use PGSQL check for server health */ -/* unused: 0x20000000 */ -/* unused 0x30000000 */ +/* unused: 0x10000000..0x30000000 */ #define PR_O2_HTTP_CHK 0x40000000 /* use HTTP 'OPTIONS' method to check server health */ #define PR_O2_MYSQL_CHK 0x50000000 /* use MYSQL check for server health */ #define PR_O2_LDAP_CHK 0x60000000 /* use LDAP check for server health */ diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index e85cde2d51..e0b1bc30d6 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -2407,63 +2407,8 @@ stats_error_parsing: goto out; } else if (!strcmp(args[1], "pgsql-check")) { - /* use PostgreSQL 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->options2 &= ~PR_O2_CHK_ANY; - curproxy->options2 |= PR_O2_PGSQL_CHK; - - if (*(args[2])) { - int cur_arg = 2; - - while (*(args[cur_arg])) { - if (strcmp(args[cur_arg], "user") == 0) { - char * packet; - uint32_t packet_len; - uint32_t pv; - - /* suboption header - needs additional argument for it */ - if (*(args[cur_arg+1]) == 0) { - ha_alert("parsing [%s:%d] : '%s %s %s' expects as argument.\n", - file, linenum, args[0], args[1], args[cur_arg]); - err_code |= ERR_ALERT | ERR_FATAL; - goto out; - } - - /* uint32_t + uint32_t + strlen("user")+1 + strlen(username)+1 + 1 */ - packet_len = 4 + 4 + 5 + strlen(args[cur_arg + 1])+1 +1; - pv = htonl(0x30000); /* protocol version 3.0 */ - - packet = calloc(1, packet_len); - - memcpy(packet + 4, &pv, 4); - - /* copy "user" */ - memcpy(packet + 8, "user", 4); - - /* copy username */ - memcpy(packet + 13, args[cur_arg+1], strlen(args[cur_arg+1])); - - free(curproxy->check_req); - curproxy->check_req = packet; - curproxy->check_len = packet_len; - - packet_len = htonl(packet_len); - memcpy(packet, &packet_len, 4); - cur_arg += 2; - } else { - /* unknown suboption - catchall */ - ha_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 */ - } - if (alertif_too_many_args_idx(2, 1, file, linenum, args, &err_code)) + err_code |= proxy_parse_pgsql_check_opt(args, 0, curproxy, &defproxy, file, linenum); + if (err_code & ERR_FATAL) goto out; } else if (!strcmp(args[1], "redis-check")) { diff --git a/src/checks.c b/src/checks.c index b0e7704803..d0452a9f8a 100644 --- a/src/checks.c +++ b/src/checks.c @@ -1156,27 +1156,6 @@ static void __event_srv_chk_r(struct conn_stream *cs) break; } - case PR_O2_PGSQL_CHK: - if (!done && b_data(&check->bi) < 9) - goto wait_more_data; - - /* do not reset when closing, servers don't like this */ - if (conn_ctrl_ready(cs->conn)) - fdtab[cs->conn->handle.fd].linger_risk = 0; - - if (b_head(&check->bi)[0] == 'R') { - set_server_check_status(check, HCHK_STATUS_L7OKD, "PostgreSQL server is ok"); - } - else { - if ((b_head(&check->bi)[0] == 'E') && (b_head(&check->bi)[5]!=0) && (b_head(&check->bi)[6]!=0)) - desc = &b_head(&check->bi)[6]; - else - desc = "PostgreSQL unknown error"; - - set_server_check_status(check, HCHK_STATUS_L7STS, desc); - } - break; - case PR_O2_MYSQL_CHK: if (!done && b_data(&check->bi) < 5) goto wait_more_data; @@ -3512,7 +3491,7 @@ static void free_tcpcheck(struct tcpcheck_rule *rule, int in_pool) } -static __maybe_unused struct tcpcheck_var *tcpcheck_var_create(const char *name) +static struct tcpcheck_var *tcpcheck_var_create(const char *name) { struct tcpcheck_var *var = NULL; @@ -5400,6 +5379,162 @@ int proxy_parse_smtpchk_opt(char **args, int cur_arg, struct proxy *curpx, struc goto out; } +/* Parses the "option pgsql-check" proxy keyword */ +int proxy_parse_pgsql_check_opt(char **args, int cur_arg, struct proxy *curpx, struct proxy *defpx, + const char *file, int line) +{ + static char pgsql_req[] = { + "%[var(check.plen),htonl,hex]" /* The packet length*/ + "00030000" /* the version 3.0 */ + "7573657200" /* "user" key */ + "%[var(check.username),hex]00" /* the username */ + "00" + }; + + struct tcpcheck_ruleset *rs = NULL; + struct tcpcheck_rules *rules = &curpx->tcpcheck_rules; + struct tcpcheck_rule *chk; + struct tcpcheck_var *var = NULL; + char *user = NULL, *errmsg = NULL; + size_t packetlen = 0; + int err_code = 0; + + if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL)) + err_code |= ERR_WARN; + + if (alertif_too_many_args_idx(2, 1, file, line, args, &err_code)) + goto out; + + if (rules->list && !(rules->flags & TCPCHK_RULES_SHARED)) { + ha_alert("parsing [%s:%d] : A custom tcp-check ruleset is already configured.\n", + file, line); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + + curpx->options2 &= ~PR_O2_CHK_ANY; + curpx->options2 |= PR_O2_TCPCHK_CHK; + + free_tcpcheck_vars(&rules->preset_vars); + rules->list = NULL; + rules->flags = 0; + + cur_arg += 2; + if (!*args[cur_arg] || !*args[cur_arg+1]) { + ha_alert("parsing [%s:%d] : '%s %s' expects 'user ' as argument.\n", + file, line, args[0], args[1]); + goto error; + } + if (strcmp(args[cur_arg], "user") == 0) { + packetlen = 15 + strlen(args[cur_arg+1]); + user = strdup(args[cur_arg+1]); + + var = tcpcheck_var_create("check.username"); + if (user == NULL || var == NULL) { + ha_alert("parsing [%s:%d] : out of memory.\n", file, line); + goto error; + } + var->data.type = SMP_T_STR; + var->data.u.str.area = user; + var->data.u.str.data = strlen(user); + LIST_INIT(&var->list); + LIST_ADDQ(&rules->preset_vars, &var->list); + user = NULL; + var = NULL; + + var = tcpcheck_var_create("check.plen"); + if (var == NULL) { + ha_alert("parsing [%s:%d] : out of memory.\n", file, line); + goto error; + } + var->data.type = SMP_T_SINT; + var->data.u.sint = packetlen; + LIST_INIT(&var->list); + LIST_ADDQ(&rules->preset_vars, &var->list); + var = NULL; + } + else { + ha_alert("parsing [%s:%d] : '%s %s' only supports optional values: 'user'.\n", + file, line, args[0], args[1]); + goto error; + } + + rs = tcpcheck_ruleset_lookup("*pgsql-check"); + if (rs) + goto ruleset_found; + + rs = tcpcheck_ruleset_create("*pgsql-check"); + if (rs == NULL) { + ha_alert("parsing [%s:%d] : out of memory.\n", file, line); + goto error; + } + + chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "linger", ""}, + 1, curpx, &rs->rules, file, line, &errmsg); + if (!chk) { + ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); + goto error; + } + chk->index = 0; + LIST_ADDQ(&rs->rules, &chk->list); + + chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", pgsql_req, "log-format", ""}, + 1, curpx, &rs->rules, file, line, &errmsg); + if (!chk) { + ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); + goto error; + } + chk->index = 1; + LIST_ADDQ(&rs->rules, &chk->list); + + chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "!rstring", "^E", + "min-recv", "5", + "error-status", "L7RSP", + "on-error", "%[check.payload(6,0)]", + ""}, + 1, curpx, &rs->rules, file, line, &errmsg); + if (!chk) { + ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); + goto error; + } + chk->index = 2; + LIST_ADDQ(&rs->rules, &chk->list); + + chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "rbinary", "^520000000800000000", + "min-recv", "9", + "error-status", "L7STS", + "on-success", "PostgreSQL server is ok", + "on-error", "PostgreSQL unknown error", + ""}, + 1, curpx, &rs->rules, file, line, &errmsg); + if (!chk) { + ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); + goto error; + } + chk->index = 3; + LIST_ADDQ(&rs->rules, &chk->list); + + LIST_ADDQ(&tcpchecks_list, &rs->list); + + ruleset_found: + rules->list = &rs->rules; + rules->flags |= (TCPCHK_RULES_SHARED|TCPCHK_RULES_PGSQL_CHK); + + out: + free(errmsg); + return err_code; + + error: + free(user); + free(var); + free_tcpcheck_vars(&rules->preset_vars); + tcpcheck_ruleset_release(rs); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; +} + + static struct cfg_kw_list cfg_kws = {ILH, { { CFG_LISTEN, "tcp-check", proxy_parse_tcpcheck }, { 0, NULL, NULL },