MAJOR: checks: Use the best mux depending on the protocol for health checks

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.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 997b2a7..5444c0a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -7376,7 +7376,9 @@
 
   "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
diff --git a/src/checks.c b/src/checks.c
index 58bc88d..29f5b2f 100644
--- a/src/checks.c
+++ b/src/checks.c
@@ -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 @@
 	}
 }
 
-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 @@
 				      "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 @@
 			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 @@
 	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 @@
 
 	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 @@
 		: ((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 @@
 		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 @@
 	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 @@
 			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 @@
 		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 @@
 	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 @@
 
 	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 @@
 	}
 
   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_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);
-
-	/* Must at least receive the status line (HTTP/1.X XXX.) */
-	if (!last_read && b_data(&check->bi) < 13)
-		goto wait_more_data;
+	last_read |= (!htx_free_space(htx) || (htx_get_tail_type(htx) == HTX_BLK_EOM));
 
-	/* 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 @@
 
 	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;
diff --git a/src/connection.c b/src/connection.c
index 42c5b23..e9a68a5 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -46,11 +46,16 @@
 	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) &&