]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MAJOR: checks: Use the best mux depending on the protocol for health checks
authorChristopher Faulet <cfaulet@haproxy.com>
Thu, 16 Apr 2020 12:50:06 +0000 (14:50 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Mon, 27 Apr 2020 08:41:07 +0000 (10:41 +0200)
When a tcp-check connect rule is evaluated, the mux protocol corresponding to
the health-check is chosen. So for TCP based health-checks, the mux-pt is
used. For HTTP based health-checks, the mux-h1 is used. The connection is marked
as private to be sure to not ruse regular HTTP connection for
health-checks. Connections reuse will be evaluated later.

The functions evaluating HTTP send rules and expect rules have been updated to
be HTX compliant. The main change for users is that HTTP health-checks are now
stricter on the HTTP message format. While before, the HTTP formatting and
parsing were minimalist, now messages should be well formatted.

doc/configuration.txt
src/checks.c
src/connection.c

index 997b2a7b9da1b483549a60a3d0944180f4df665d..5444c0a2c8c840d6beb7795cb281c218f6e9b3ce 100644 (file)
@@ -7376,7 +7376,9 @@ option httpchk <method> <uri> <version>
 
   "httpchk" option does not necessarily require an HTTP backend, it also works
   with plain TCP backends. This is particularly useful to check simple scripts
-  bound to some dedicated ports using the inetd daemon.
+  bound to some dedicated ports using the inetd daemon. However, it will always
+  internally relies on an HTX mutliplexer. Thus, it means the request
+  formatting and the response parsing will be strict.
 
   Note : For a while, there was no way to add headers or body in the request
          used for HTTP health checks. So a workaround was to hide it at the end
index 58bc88dc4cab4ae737446f20815cd13ee15dd487..29f5b2f9ca143105bfb4c30c10bd7522d83a074a 100644 (file)
@@ -40,6 +40,7 @@
 #include <common/hathreads.h>
 #include <common/http.h>
 #include <common/h1.h>
+#include <common/htx.h>
 
 #include <types/global.h>
 #include <types/dns.h>
@@ -51,6 +52,7 @@
 #include <proto/checks.h>
 #include <proto/stats.h>
 #include <proto/fd.h>
+#include <proto/http_htx.h>
 #include <proto/log.h>
 #include <proto/mux_pt.h>
 #include <proto/queue.h>
@@ -500,11 +502,10 @@ void __health_adjust(struct server *s, short status)
        }
 }
 
-static int httpchk_build_status_header(struct server *s, char *buffer, int size)
+static int httpchk_build_status_header(struct server *s, struct buffer *buf)
 {
        int sv_state;
        int ratio;
-       int hlen = 0;
        char addr[46];
        char port[6];
        const char *srv_hlt_st[7] = { "DOWN", "DOWN %d/%d",
@@ -512,9 +513,6 @@ static int httpchk_build_status_header(struct server *s, char *buffer, int size)
                                      "NOLB %d/%d", "NOLB",
                                      "no check" };
 
-       memcpy(buffer + hlen, "X-Haproxy-Server-State: ", 24);
-       hlen += 24;
-
        if (!(s->check.state & CHK_ST_ENABLED))
                sv_state = 6;
        else if (s->cur_state != SRV_ST_STOPPED) {
@@ -532,10 +530,9 @@ static int httpchk_build_status_header(struct server *s, char *buffer, int size)
                        sv_state = 0; /* DOWN */
        }
 
-       hlen += snprintf(buffer + hlen, size - hlen,
-                            srv_hlt_st[sv_state],
-                            (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
-                            (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
+       chunk_appendf(buf, srv_hlt_st[sv_state],
+                     (s->cur_state != SRV_ST_STOPPED) ? (s->check.health - s->check.rise + 1) : (s->check.health),
+                     (s->cur_state != SRV_ST_STOPPED) ? (s->check.fall) : (s->check.rise));
 
        addr_to_str(&s->addr, addr, sizeof(addr));
        if (s->addr.ss_family == AF_INET || s->addr.ss_family == AF_INET6)
@@ -543,25 +540,22 @@ static int httpchk_build_status_header(struct server *s, char *buffer, int size)
        else
                *port = 0;
 
-       hlen += snprintf(buffer + hlen,  size - hlen, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
-                            addr, port, s->proxy->id, s->id,
-                            global.node,
-                            (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
-                            (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
-                            s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
-                            s->nbpend);
+       chunk_appendf(buf, "; address=%s; port=%s; name=%s/%s; node=%s; weight=%d/%d; scur=%d/%d; qcur=%d",
+                     addr, port, s->proxy->id, s->id,
+                     global.node,
+                     (s->cur_eweight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
+                     (s->proxy->lbprm.tot_weight * s->proxy->lbprm.wmult + s->proxy->lbprm.wdiv - 1) / s->proxy->lbprm.wdiv,
+                     s->cur_sess, s->proxy->beconn - s->proxy->nbpend,
+                     s->nbpend);
 
        if ((s->cur_state == SRV_ST_STARTING) &&
            now.tv_sec < s->last_change + s->slowstart &&
            now.tv_sec >= s->last_change) {
                ratio = MAX(1, 100 * (now.tv_sec - s->last_change) / s->slowstart);
-               hlen += snprintf(buffer + hlen, size - hlen, "; throttle=%d%%", ratio);
+               chunk_appendf(buf, "; throttle=%d%%", ratio);
        }
 
-       buffer[hlen++] = '\r';
-       buffer[hlen++] = '\n';
-
-       return hlen;
+       return b_data(buf);
 }
 
 /* Check the connection. If an error has already been reported or the socket is
@@ -2708,6 +2702,7 @@ static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct
 
        check->cs = cs;
        conn = cs->conn;
+       conn_set_owner(conn, check->sess, NULL);
 
        /* Maybe there were an older connection we were waiting on */
        check->wait_list.events = 0;
@@ -2754,10 +2749,6 @@ static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct
                : ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) ? check->xprt : xprt_get(XPRT_RAW)));
 
        conn_prepare(conn, proto, xprt);
-       if (conn_install_mux(conn, &mux_pt_ops, cs, proxy, check->sess) < 0) {
-               status = SF_ERR_RESOURCE;
-               goto fail_check;
-       }
        cs_attach(cs, check, &check_conn_cb);
 
        status = SF_ERR_INTERNAL;
@@ -2771,18 +2762,46 @@ static enum tcpcheck_eval_ret tcpcheck_eval_connect(struct check *check, struct
                status = proto->connect(conn, flags);
        }
 
-#ifdef USE_OPENSSL
-       if (status == SF_ERR_NONE) {
-               if (connect->sni)
-                       ssl_sock_set_servername(conn, connect->sni);
-               else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
-                       ssl_sock_set_servername(conn, s->check.sni);
+       if (status != SF_ERR_NONE)
+               goto fail_check;
+
+       conn->flags |= CO_FL_PRIVATE;
+       conn->ctx = cs;
+
+       /* The mux may be initialized now if there isn't server attached to the
+        * check (email alerts) or if there is a mux proto specified or if there
+        * is no alpn.
+        */
+       if (!s || connect->mux_proto || check->mux_proto || (!connect->alpn && !check->alpn_str)) {
+               const struct mux_ops *mux_ops;
 
-               if (connect->alpn)
-                       ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
-               else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
-                       ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
+               if (connect->mux_proto)
+                       mux_ops = connect->mux_proto->mux;
+               else if (check->mux_proto)
+                       mux_ops = check->mux_proto->mux;
+               else {
+                       int mode = ((check->tcpcheck_rules->flags & TCPCHK_RULES_PROTO_CHK) == TCPCHK_RULES_HTTP_CHK
+                                   ? PROTO_MODE_HTTP
+                                   : PROTO_MODE_TCP);
+
+                       mux_ops = conn_get_best_mux(conn, IST_NULL, PROTO_SIDE_BE, mode);
+               }
+               if (mux_ops && conn_install_mux(conn, mux_ops, cs, proxy, check->sess) < 0) {
+                       status = SF_ERR_INTERNAL;
+                       goto fail_check;
+               }
        }
+
+#ifdef USE_OPENSSL
+       if (connect->sni)
+               ssl_sock_set_servername(conn, connect->sni);
+       else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.sni)
+               ssl_sock_set_servername(conn, s->check.sni);
+
+       if (connect->alpn)
+               ssl_sock_set_alpn(conn, (unsigned char *)connect->alpn, connect->alpn_len);
+       else if ((connect->options & TCPCHK_OPT_DEFAULT_CONNECT) && s->check.alpn_str)
+               ssl_sock_set_alpn(conn, (unsigned char *)s->check.alpn_str, s->check.alpn_len);
 #endif
        if ((connect->options & TCPCHK_OPT_SOCKS4) && (s->flags & SRV_F_SOCKS4_PROXY)) {
                conn->send_proxy_ofs = 1;
@@ -2879,6 +2898,7 @@ static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcp
        struct conn_stream *cs = check->cs;
        struct connection *conn = cs_conn(cs);
        struct buffer *tmp = NULL;
+       struct htx *htx = NULL;
 
        /* reset the read & write buffer */
        b_reset(&check->bi);
@@ -2915,7 +2935,13 @@ static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcp
                        goto error_lf;
                break;
        case TCPCHK_SEND_HTTP: {
-               struct ist meth, uri, vsn;
+               struct htx_sl *sl;
+               struct ist meth, uri, vsn, clen, body;
+               unsigned int slflags = 0;
+
+               tmp = alloc_trash_chunk();
+               if (!tmp)
+                       goto error_htx;
 
                meth = ((send->http.meth.meth == HTTP_METH_OTHER)
                        ? ist2(send->http.meth.str.area, send->http.meth.str.data)
@@ -2923,43 +2949,52 @@ static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcp
                uri = (isttest(send->http.uri) ? send->http.uri : ist("/")); // TODO: handle uri_fmt
                vsn = (isttest(send->http.vsn) ? send->http.vsn : ist("HTTP/1.0"));
 
-               chunk_istcat(&check->bo, meth);
-               check->bo.area[check->bo.data++] = ' ';
-               chunk_istcat(&check->bo, uri);
-               check->bo.area[check->bo.data++] = ' ';
-               chunk_istcat(&check->bo, vsn);
-               chunk_istcat(&check->bo, ist("\r\n"));
-               chunk_istcat(&check->bo, ist("Connection: close\r\n"));
-               if (isttest(send->http.body)) {
-                       // TODO: handle body_fmt
-                       chunk_appendf(&check->bo, "Content-Length: %s\r\n", ultoa(istlen(send->http.body)));
-               }
-               if (check->proxy->options2 & PR_O2_CHK_SNDST) {
-                       trash.data = httpchk_build_status_header(check->server, b_orig(&trash), b_size(&trash));
-                       chunk_cat(&check->bo, &trash);
-               }
+               if (istlen(vsn) == 8 &&
+                   (*(vsn.ptr+5) > '1' || (*(vsn.ptr+5) == '1' && *(vsn.ptr+7) >= '1')))
+                       slflags |= HTX_SL_F_VER_11;
+               slflags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
+               if (!isttest(send->http.body))
+                       slflags |= HTX_SL_F_BODYLESS;
+
+               htx = htx_from_buf(&check->bo);
+               sl = htx_add_stline(htx, HTX_BLK_REQ_SL, slflags, meth, uri, vsn);
+               if (!sl)
+                       goto error_htx;
+               sl->info.req.meth = send->http.meth.meth;
+
+               body = send->http.body; // TODO: handle body_fmt
+               clen = ist((!istlen(body) ? "0" : ultoa(istlen(body))));
+
+               if (!htx_add_header(htx, ist("Connection"), ist("close")) ||
+                   !htx_add_header(htx, ist("Content-length"), clen))
+                       goto error_htx;
+
                if (!LIST_ISEMPTY(&send->http.hdrs)) {
                        struct tcpcheck_http_hdr *hdr;
 
-                       tmp = alloc_trash_chunk();
-                       if (!tmp)
-                               goto error_lf;
                        list_for_each_entry(hdr, &send->http.hdrs, list) {
                                chunk_reset(tmp);
                                 tmp->data = sess_build_logline(check->sess, NULL, b_orig(tmp), b_size(tmp), &hdr->value);
                                if (!b_data(tmp))
                                        continue;
-                               chunk_istcat(&check->bo, hdr->name);
-                               check->bo.area[check->bo.data++] = ' ';
-                               chunk_cat(&check->bo, tmp);
-                               chunk_istcat(&check->bo, ist("\r\n"));
+                               if (!htx_add_header(htx, hdr->name, ist2(b_orig(tmp), b_data(tmp))))
+                                       goto error_htx;
                        }
+
                }
-               chunk_istcat(&check->bo, ist("\r\n"));
-               if (isttest(send->http.body)) {
-                       // TODO: handle body_fmt
-                       chunk_istcat(&check->bo, send->http.body);
+               if (check->proxy->options2 & PR_O2_CHK_SNDST) {
+                       chunk_reset(tmp);
+                       httpchk_build_status_header(check->server, tmp);
+                       if (!htx_add_header(htx, ist("X-Haproxy-Server-State"), ist2(b_orig(tmp), b_data(tmp))))
+                               goto error_htx;
                }
+
+               if (!htx_add_endof(htx, HTX_BLK_EOH) ||
+                   (istlen(body) && !htx_add_data_atonce(htx, send->http.body)) ||
+                   !htx_add_endof(htx, HTX_BLK_EOM))
+                       goto error_htx;
+
+               htx_to_buf(htx, &check->bo);
                break;
        }
        case TCPCHK_SEND_UNDEF:
@@ -2984,6 +3019,17 @@ static enum tcpcheck_eval_ret tcpcheck_eval_send(struct check *check, struct tcp
        free_trash_chunk(tmp);
        return ret;
 
+  error_htx:
+       if (htx) {
+               htx_reset(htx);
+               htx_to_buf(htx, &check->bo);
+       }
+       chunk_printf(&trash, "tcp-check send : failed to build HTTP request at step %d",
+                    tcpcheck_get_step_id(check, rule));
+       set_server_check_status(check, HCHK_STATUS_L7RSP, trash.area);
+       ret = TCPCHK_EVAL_STOP;
+       goto out;
+
   error_lf:
        chunk_printf(&trash, "tcp-check send : failed to build log-format string at step %d",
                     tcpcheck_get_step_id(check, rule));
@@ -3016,7 +3062,7 @@ static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcp
 
        while ((cs->flags & CS_FL_RCV_MORE) ||
               (!(conn->flags & CO_FL_ERROR) && !(cs->flags & (CS_FL_ERROR|CS_FL_EOS)))) {
-               max = b_room(&check->bi);
+               max = (IS_HTX_CS(cs) ?  htx_free_space(htxbuf(&check->bi)) : b_room(&check->bi));
                read = conn->mux->rcv_buf(cs, &check->bi, max, 0);
                cur_read += read;
                if (!read ||
@@ -3027,7 +3073,7 @@ static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcp
        }
 
   end_recv:
-       is_empty = !b_data(&check->bi);
+       is_empty = (IS_HTX_CS(cs) ? htx_is_empty(htxbuf(&check->bi)) : !b_data(&check->bi));
        if (is_empty && ((conn->flags & CO_FL_ERROR) || (cs->flags & CS_FL_ERROR))) {
                /* Report network errors only if we got no other data. Otherwise
                 * we'll let the upper layers decide whether the response is OK
@@ -3065,33 +3111,33 @@ static enum tcpcheck_eval_ret tcpcheck_eval_recv(struct check *check, struct tcp
 
 static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, struct tcpcheck_rule *rule, int last_read)
 {
+       struct htx *htx = htxbuf(&check->bi);
+       struct htx_sl *sl;
+       struct htx_blk *blk;
        enum tcpcheck_eval_ret ret = TCPCHK_EVAL_CONTINUE;
        struct tcpcheck_expect *expect = &rule->expect;
        struct buffer *msg = NULL;
        enum healthcheck_status status;
        struct ist desc = ist(NULL);
-       char *body;
-       size_t body_len;
        int match, inverse;
 
-       last_read |= b_full(&check->bi);
+       last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
 
-       /* Must at least receive the status line (HTTP/1.X XXX.) */
-       if (!last_read && b_data(&check->bi) < 13)
-               goto wait_more_data;
-
-       /* Check if the server speaks HTTP 1.X */
-       if (b_data(&check->bi) < 13 ||
-           memcmp(b_head(&check->bi), "HTTP/1.", 7) != 0 ||
-           (*b_peek(&check->bi, 12) != ' ' && *b_peek(&check->bi, 12) != '\r') ||
-           !isdigit((unsigned char) *b_peek(&check->bi, 9)) || !isdigit((unsigned char) *b_peek(&check->bi, 10)) ||
-           !isdigit((unsigned char) *b_peek(&check->bi, 11))) {
+       if (htx->flags & HTX_FL_PARSING_ERROR) {
                status = HCHK_STATUS_L7RSP;
-               desc   = ist2(b_head(&check->bi), my_memcspn(b_head(&check->bi), b_data(&check->bi), "\r\n", 2));
                goto error;
        }
 
-       check->code = strl2uic(b_peek(&check->bi, 9), 3);
+       if (htx_is_empty(htx)) {
+               if (last_read) {
+                       status = HCHK_STATUS_L7RSP;
+                       goto error;
+               }
+               goto wait_more_data;
+       }
+
+       sl = http_get_stline(htx);
+       check->code = sl->info.res.status;
 
        if (check->server &&
            (check->server->proxy->options & PR_O_DISABLE404) &&
@@ -3107,44 +3153,53 @@ static enum tcpcheck_eval_ret tcpcheck_eval_expect_http(struct check *check, str
 
        switch (expect->type) {
        case TCPCHK_EXPECT_HTTP_STATUS:
-               match = my_memmem(b_peek(&check->bi, 9), 3, expect->data.ptr, istlen(expect->data)) != NULL;
+               match = isteq(htx_sl_res_code(sl), expect->data);
 
                /* Set status and description in case of error */
                status = HCHK_STATUS_L7STS;
-               desc  = ist2(b_peek(&check->bi, 12), my_memcspn(b_peek(&check->bi, 12), b_data(&check->bi) - 12, "\r\n", 2));
+               desc   = htx_sl_res_reason(sl);
                break;
        case TCPCHK_EXPECT_HTTP_REGEX_STATUS:
-               match = regex_exec2(expect->regex, b_peek(&check->bi, 9), 3);
+               match = regex_exec2(expect->regex, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl));
 
                /* Set status and description in case of error */
                status = HCHK_STATUS_L7STS;
-               desc  = ist2(b_peek(&check->bi, 12), my_memcspn(b_peek(&check->bi, 12), b_data(&check->bi) - 12, "\r\n", 2));
+               desc   = htx_sl_res_reason(sl);
                break;
+
        case TCPCHK_EXPECT_HTTP_BODY:
        case TCPCHK_EXPECT_HTTP_REGEX_BODY:
-               body = (char *)my_memmem(b_head(&check->bi), b_data(&check->bi), "\r\n\r\n", 4);
-               if (!body) {
+               chunk_reset(&trash);
+               for (blk = htx_get_head_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
+                       enum htx_blk_type type = htx_get_blk_type(blk);
+
+                       if (type == HTX_BLK_EOM || type == HTX_BLK_TLR || type == HTX_BLK_EOT)
+                               break;
+                       if (type == HTX_BLK_DATA) {
+                               if (!chunk_istcat(&trash, htx_get_blk_value(htx, blk)))
+                                       break;
+                       }
+               }
+
+               if (!b_data(&trash)) {
                        if (!last_read)
                                goto wait_more_data;
-
                        status = HCHK_STATUS_L7RSP;
                        desc = ist("HTTP content check could not find a response body");
                        goto error;
                }
-               body += 4;
-               body_len = b_tail(&check->bi) - body;
 
                if (!last_read &&
-                   ((expect->type == TCPCHK_EXPECT_HTTP_BODY && body_len < istlen(expect->data)) ||
-                    (expect->min_recv > 0 && body_len < expect->min_recv))) {
+                   ((expect->type == TCPCHK_EXPECT_HTTP_BODY && b_data(&trash) < istlen(expect->data)) ||
+                    (expect->min_recv > 0 && b_data(&trash) < expect->min_recv))) {
                        ret = TCPCHK_EVAL_WAIT;
                        goto out;
                }
 
                if (expect->type ==TCPCHK_EXPECT_HTTP_BODY)
-                       match = my_memmem(body, body_len, expect->data.ptr, istlen(expect->data)) != NULL;
+                       match = my_memmem(b_orig(&trash), b_data(&trash), expect->data.ptr, istlen(expect->data)) != NULL;
                else
-                       match = regex_exec2(expect->regex, body, body_len);
+                       match = regex_exec2(expect->regex, b_orig(&trash), b_data(&trash));
 
                /* Set status and description in case of error */
                status = HCHK_STATUS_L7RSP;
index 42c5b23a8fc77075f002e923c20fb98ee14fee3b..e9a68a578d56070f99d0faa9727af0f96fd1ccd8 100644 (file)
@@ -46,11 +46,16 @@ int conn_create_mux(struct connection *conn)
        if (conn_is_back(conn)) {
                struct server *srv;
                struct conn_stream *cs = conn->ctx;
+               struct session *sess = conn->owner;
 
                if (conn->flags & CO_FL_ERROR)
                        goto fail;
 
-               if (conn_install_mux_be(conn, conn->ctx, conn->owner) < 0)
+               if (sess && obj_type(sess->origin) == OBJ_TYPE_CHECK) {
+                       if (conn_install_mux_chk(conn, conn->ctx, conn->owner) < 0)
+                               goto fail;
+               }
+               else if (conn_install_mux_be(conn, conn->ctx, conn->owner) < 0)
                        goto fail;
                srv = objt_server(conn->target);
                if (srv && ((srv->proxy->options & PR_O_REUSE_MASK) != PR_O_REUSE_NEVR) &&