]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: rawsock: introduce CO_RFL_TRY_HARDER to detect closures on complete reads
authorWilly Tarreau <w@1wt.eu>
Mon, 29 Sep 2025 12:05:55 +0000 (14:05 +0200)
committerWilly Tarreau <w@1wt.eu>
Wed, 1 Oct 2025 08:23:01 +0000 (10:23 +0200)
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
src/raw_sock.c

index 835c70b81b7ac9ad4c133a4dfaf2af4731da4298..88534dc6375bcf68d0a96bfd66f1254c44e31347 100644 (file)
@@ -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() */
index 5ef904d746109689b16141c4faa083c5ab5f43bf..eebdf8be8fdc26ece736638b260f95b60d5c7a9f 100644 (file)
@@ -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;