From 1afaa7b59d6c407d68683b821f3142654a37b17e Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Mon, 29 Sep 2025 14:05:55 +0200 Subject: [PATCH] MINOR: rawsock: introduce CO_RFL_TRY_HARDER to detect closures on complete reads Normally, when reading a full buffer, or exactly the requested size, it is not really possible to know if the peer had closed immediately after, and usually we don't care. There's a problematic case, though, which is with SSL: the SSL layer reads in small chunks of a few bytes, and can consume a client_hello this way, then start computation without knowing yet that the client has aborted. In order to permit knowing more, we now introduce a new read flag, CO_RFL_TRY_HARDER, which says that if we've read up to the permitted limit and the flag is set, then we attempt one extra byte using MSG_PEEK to detect whether the connection was closed immediately after that content or not. The first use case will obviously be related to SSL and client_hello, but it might possibly also make sense on HTTP responses to detect a pending FIN at the end of a response (e.g. if a close was already advertised). --- include/haproxy/connection-t.h | 1 + src/raw_sock.c | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/include/haproxy/connection-t.h b/include/haproxy/connection-t.h index 835c70b81..88534dc63 100644 --- a/include/haproxy/connection-t.h +++ b/include/haproxy/connection-t.h @@ -329,6 +329,7 @@ enum { CO_RFL_KEEP_RECV = 0x0008, /* Instruct the mux to still wait for read events */ CO_RFL_BUF_NOT_STUCK = 0x0010, /* Buffer is not stuck. Optims are possible during data copy */ CO_RFL_MAY_SPLICE = 0x0020, /* The producer can use the kernel splicing */ + CO_RFL_TRY_HARDER = 0x0040, /* Try to read till READ0 even on short reads */ }; /* flags that can be passed to xprt->snd_buf() and mux->snd_buf() */ diff --git a/src/raw_sock.c b/src/raw_sock.c index 5ef904d74..eebdf8be8 100644 --- a/src/raw_sock.c +++ b/src/raw_sock.c @@ -330,9 +330,11 @@ static size_t raw_sock_to_buf(struct connection *conn, void *xprt_ctx, struct bu goto read0; } - if (!(fdtab[conn->handle.fd].state & FD_LINGER_RISK) || - (cur_poller.flags & HAP_POLL_F_RDHUP)) { - break; + if (!(flags & CO_RFL_TRY_HARDER)) { + if (!(fdtab[conn->handle.fd].state & FD_LINGER_RISK) || + (cur_poller.flags & HAP_POLL_F_RDHUP)) { + break; + } } } count -= ret; @@ -360,6 +362,31 @@ static size_t raw_sock_to_buf(struct connection *conn, void *xprt_ctx, struct bu if (unlikely(conn->flags & CO_FL_WAIT_L4_CONN) && done) conn->flags &= ~CO_FL_WAIT_L4_CONN; + if (unlikely((flags & CO_RFL_TRY_HARDER) && + !(conn->flags & CO_FL_SOCK_RD_SH) && + !count)) { + /* we've read exactly what was being asked for, which is loewr + * than a full buffer, and the caller wants us to really check + * if there's something after. This happens in the context of + * SSL where the lib reads in tiny chunks without offering the + * ability to detect a pending close. Let's just check using + * MSG_PEEK so that we don't pull bytes we shouldn't. + */ + char c; + + ret = recv(conn->handle.fd, &c, 1, MSG_PEEK); + if (ret == 0) { + conn_report_term_evt(conn, tevt_loc_fd, fd_tevt_type_shutr); + goto read0; + } + else if (ret < 0 && + (errno != EAGAIN && errno != EWOULDBLOCK && errno != ENOTCONN && errno != EINTR)) { + conn_report_term_evt(conn, tevt_loc_fd, fd_tevt_type_rcv_err); + conn->flags |= CO_FL_ERROR | CO_FL_SOCK_RD_SH | CO_FL_SOCK_WR_SH; + conn_set_errno(conn, errno); + } + } + leave: return done; -- 2.47.3