]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: tcp-check: properly indicate polling state before performing I/O
authorWilly Tarreau <w@1wt.eu>
Wed, 4 Oct 2017 06:45:19 +0000 (08:45 +0200)
committerWilly Tarreau <w@1wt.eu>
Wed, 4 Oct 2017 11:41:17 +0000 (13:41 +0200)
While porting the connection to use the mux layer, it appeared that
tcp-checks wouldn't receive anymore because the polling is not enabled
before attempting to call xprt->rcv_buf() nor xprt->snd_buf(), and it
is illegal to call these functions with polling disabled as they
directly manipulate the FD state, resulting in an inconsistency where
the FD is enabled and the connection's polling flags disabled.

Till now it happened to work only because when recv() fails on EAGAIN
it calls fd_cant_recv() which enables polling while signaling the
failure, so that next time the message is received. But the connection's
polling is never enabled, and any tiny change resulting in a call to
conn_data_update_polling() immediately disables reading again.

It's likely that this problem already happens on some corner cases
such as multi-packet responses. It definitely breaks as soon as the
response buffer is full but we don't support consuming more than one
response buffer.

This fix should be backported to 1.7 and 1.6. In order to check for the
proper behaviour, this tcp-check must work and clearly show an SSH
banner in recvfrom() as observed under strace, otherwise it's broken :

   tcp-check connect port 22
   tcp-check expect rstring SSH
   tcp-check send blah

src/checks.c

index 539d5cc1bda672f5a4071dd2616192215e9801e5..31aa6081a349eea51221f27f837494520d5368a0 100644 (file)
@@ -2591,6 +2591,7 @@ static void tcpcheck_main(struct connection *conn)
                     check->current_step->action != TCPCHK_ACT_SEND ||
                     check->current_step->string_len >= buffer_total_space(check->bo))) {
 
+                       __conn_data_want_send(conn);
                        if (conn->xprt->snd_buf(conn, check->bo, 0) <= 0) {
                                if (conn->flags & CO_FL_ERROR) {
                                        chk_report_conn_err(conn, errno, 0);
@@ -2792,6 +2793,7 @@ static void tcpcheck_main(struct connection *conn)
                        if (unlikely(check->result == CHK_RES_FAILED))
                                goto out_end_tcpcheck;
 
+                       __conn_data_want_recv(conn);
                        if (conn->xprt->rcv_buf(conn, check->bi, check->bi->size) <= 0) {
                                if (conn->flags & (CO_FL_ERROR | CO_FL_SOCK_RD_SH)) {
                                        done = 1;