From: Christopher Faulet Date: Mon, 22 Jul 2024 17:00:42 +0000 (+0200) Subject: MEDIUM: spoe/tcpcheck: Reintroduce SPOP check as a customized tcp-check X-Git-Tag: v3.1-dev4~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=51e18c9aa6ecc0e10af51c99f111720538c7177e;p=thirdparty%2Fhaproxy.git MEDIUM: spoe/tcpcheck: Reintroduce SPOP check as a customized tcp-check To be able to retrieve accurrate errors when a SPOP health-check is performed, a customized tcp-check is used. Indeed, it is not possible to rely on the SPOP multiplexer for now because the check is performed at the mux connection layer and the error, if any, cannot be retrieved by the health-check. A L4 success or error is reported. To fix this issue and restore the previous behavior, a customized tcp-check is created. The connection is forced to use the PT multiplexer. An hardcoded message is sent and a customer handler is used to decode the SPOA response. This way, it is possible to parse the response and return an accurrate status code. --- diff --git a/src/tcpcheck.c b/src/tcpcheck.c index ccfc797625..5e3775ba6a 100644 --- a/src/tcpcheck.c +++ b/src/tcpcheck.c @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -759,6 +760,213 @@ enum tcpcheck_eval_ret tcpcheck_ldap_expect_bindrsp(struct check *check, struct goto out; } +/* Custom tcp-check expect function to parse and validate the SPOP HELLO + * response packet. Returns TCPCHK_EVAL_WAIT to wait for more data, + * TCPCHK_EVAL_CONTINUE to evaluate the next rule or TCPCHK_EVAL_STOP if an + * error occurred. + */ +enum tcpcheck_eval_ret tcpcheck_spop_expect_hello(struct check *check, struct tcpcheck_rule *rule, int last_read) +{ + enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE; + enum healthcheck_status status; + struct buffer *msg = NULL; + struct ist desc = IST_NULL; + char *ptr, *end; + unsigned int type, flags, flen = 0; + int vsn, max_frame_size, caps; + + + TRACE_ENTER(CHK_EV_TCPCHK_EXP, check); + + /* 4 Bytes for the packet length, 1 byte for the frame type, 4bytes for flags, 2 bytes for the stream and frame id */ + if (b_data(&check->bi) < 11) + goto too_short; + + ptr = b_head(&check->bi); + flen = read_n32(ptr); + type = (unsigned int)*(ptr+4); + flags = read_n32(ptr+5); + if (type != 101 && type != 102) { + /* Frame type must be AGENT-HELLO or AGENT-DISCONNECT*/ + goto invalid_frame; + } + if (flags != 0x01) { + /* Only FIN must be set */ + goto invalid_frame; + } + if (*(ptr+9) != 0 || *(ptr+10) != 0) + goto invalid_frame; + + if (b_data(&check->bi) < 4 + flen) + goto too_short; + + ptr += 11; + end = b_tail(&check->bi); + if (*ptr == 102) { + /* AGENT-DISCONNECT frame*/ + while (ptr < end) { + char *str; + uint64_t sz; + int ret; + + /* Decode the item key */ + ret = spoe_decode_buffer(&ptr, end, &str, &sz); + if (ret == -1 || !sz) + goto invalid_frame; + + /* Check "status-code" K/V item */ + if (isteq(ist2(str, sz), ist("status-code"))) { + int type = *ptr++; + + /* The value must be an integer */ + if ((type & SPOP_DATA_T_MASK) != SPOP_DATA_T_INT32 && + (type & SPOP_DATA_T_MASK) != SPOP_DATA_T_INT64 && + (type & SPOP_DATA_T_MASK) != SPOP_DATA_T_UINT32 && + (type & SPOP_DATA_T_MASK) != SPOP_DATA_T_UINT64) + goto invalid_frame; + if (decode_varint(&ptr, end, &sz) == -1) + goto invalid_frame; + check->code = sz; + } + + /* Check "message" K/V item */ + else if (isteq(ist2(str, sz), ist("message"))) { + int type = *ptr++; + + /* The value must be a string */ + if ((type & SPOP_DATA_T_MASK) != SPOP_DATA_T_STR) + goto invalid_frame; + ret = spoe_decode_buffer(&ptr, end, &str, &sz); + if (ret == -1 || sz > 255) + goto invalid_frame; + desc = ist2(str, sz); + } + else { + /* Silently ignore unknown item */ + if (spoe_skip_data(&ptr, end) == -1) + goto invalid_frame; + } + } + goto error; + } + + /* AGENT-HELLO frame*/ + vsn = max_frame_size = caps = 0; + while (ptr < end) { + char *str; + uint64_t sz; + int ret; + + /* Decode the item key */ + ret = spoe_decode_buffer(&ptr, end, &str, &sz); + if (ret == -1 || !sz) + goto invalid_frame; + + /* Check "version" K/V item */ + if (isteq(ist2(str, sz), ist("version"))) { + int type = *ptr++; + + /* The value must be a string */ + if ((type & SPOP_DATA_T_MASK) != SPOP_DATA_T_STR) + goto invalid_frame; + if (spoe_decode_buffer(&ptr, end, &str, &sz) == -1) + goto invalid_frame; + + vsn = spoe_str_to_vsn(str, sz); + if (vsn == -1) { + check->code = SPOP_ERR_INVALID; + goto error; + } + if (spoe_check_vsn(vsn) == -1) { + check->code = SPOP_ERR_BAD_VSN; + goto error; + } + } + /* Check "max-frame-size" K/V item */ + else if (isteq(ist2(str, sz), ist("max-frame-size"))) { + int type = *ptr++; + + /* The value must be integer */ + if ((type & SPOP_DATA_T_MASK) != SPOP_DATA_T_INT32 && + (type & SPOP_DATA_T_MASK) != SPOP_DATA_T_INT64 && + (type & SPOP_DATA_T_MASK) != SPOP_DATA_T_UINT32 && + (type & SPOP_DATA_T_MASK) != SPOP_DATA_T_UINT64) + goto invalid_frame; + if (decode_varint(&ptr, end, &sz) == -1) + goto invalid_frame; + if (sz < SPOP_MIN_FRAME_SIZE || sz > SPOP_MAX_FRAME_SIZE) { + check->code = SPOP_ERR_BAD_FRAME_SIZE; + goto error; + } + max_frame_size = sz; + } + /* Check "capabilities" K/V item */ + else if (isteq(ist2(str, sz), ist("capabilities"))) { + int type = *ptr++; + + /* The value must be a string */ + if ((type & SPOP_DATA_T_MASK) != SPOP_DATA_T_STR) + goto invalid_frame; + if (spoe_decode_buffer(&ptr, end, &str, &sz) == -1) + goto invalid_frame; + /* Capabilities value not checked */ + caps = 1; + } + else { + /* Silently ignore unknown item */ + if (spoe_skip_data(&ptr, end) == -1) + goto invalid_frame; + } + } + if (!vsn) { + check->code = SPOP_ERR_NO_VSN; + goto error; + } + else if (!max_frame_size) { + check->code = SPOP_ERR_NO_FRAME_SIZE; + goto error; + } + else if (!caps) { + check->code = SPOP_ERR_NO_CAP; + goto error; + } + + status = ((rule->expect.ok_status != HCHK_STATUS_UNKNOWN) ? rule->expect.ok_status : HCHK_STATUS_L7OKD); + set_server_check_status(check, status, "SPOA server is ok"); + + out: + free_trash_chunk(msg); + TRACE_LEAVE(CHK_EV_TCPCHK_EXP, check, 0, 0, (size_t[]){ret}); + return ret; + + error: + ret = TCPCHK_EVAL_STOP; + status = HCHK_STATUS_L7RSP; + msg = alloc_trash_chunk(); + if (msg) { + if (!isttest(desc)) + desc = spop_err_reasons[check->code]; + tcpcheck_expect_onerror_message(msg, check, rule, 0, desc); + } + set_server_check_status(check, status, (msg ? b_head(msg) : NULL)); + goto out; + + invalid_frame: + check->code = SPOP_ERR_INVALID; + goto error; + + too_short: + if (!last_read) + goto wait_more_data; + /* invalid length or truncated response */ + goto invalid_frame; + + wait_more_data: + TRACE_DEVEL("waiting for more data", CHK_EV_TCPCHK_EXP, check); + ret = TCPCHK_EVAL_WAIT; + goto out; +} + /* Custom tcp-check expect function to parse and validate the agent-check * reply. Returns TCPCHK_EVAL_WAIT to wait for more data, TCPCHK_EVAL_CONTINUE * to evaluate the next rule or TCPCHK_EVAL_STOP if an error occurred. @@ -4789,8 +4997,17 @@ int proxy_parse_ldap_check_opt(char **args, int cur_arg, struct proxy *curpx, co int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, const struct proxy *defpx, const char *file, int line) { + static char *spop_req = + "0000004e0100000001" /* frame length (4-bytes) + type (1-bytes) + flags (4-bytes)*/ + "0000" /* stream-id + frame-id (unset, 2-bytes) */ + "12737570706f727465642d76657273696f6e730803322e30" + "0e6d61782d6672616d652d73697a6503fcf0060c63617061" + "62696c697469657308000b6865616c7468636865636b11"; + struct tcpcheck_ruleset *rs = NULL; struct tcpcheck_rules *rules = &curpx->tcpcheck_rules; + struct tcpcheck_rule *chk; + char *errmsg = NULL; int err_code = 0; if (warnifnotcap(curpx, PR_CAP_BE, file, line, args[cur_arg+1], NULL)) @@ -4816,6 +5033,34 @@ int proxy_parse_spop_check_opt(char **args, int cur_arg, struct proxy *curpx, co goto error; } + chk = parse_tcpcheck_connect((char *[]){"tcp-check", "connect", "default", "proto", "none", ""}, + 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_APPEND(&rs->rules, &chk->list); + + chk = parse_tcpcheck_send((char *[]){"tcp-check", "send-binary", spop_req, ""}, + 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_APPEND(&rs->rules, &chk->list); + + chk = parse_tcpcheck_expect((char *[]){"tcp-check", "expect", "custom", ""}, + 1, curpx, &rs->rules, TCPCHK_RULES_SPOP_CHK, file, line, &errmsg); + if (!chk) { + ha_alert("parsing [%s:%d] : %s\n", file, line, errmsg); + goto error; + } + chk->expect.custom = tcpcheck_spop_expect_hello; + chk->index = 2; + LIST_APPEND(&rs->rules, &chk->list); + ruleset_found: rules->list = &rs->rules; rules->flags &= ~(TCPCHK_RULES_PROTO_CHK|TCPCHK_RULES_UNUSED_RS);