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).
This commit is contained in:
Willy Tarreau 2025-09-29 14:05:55 +02:00
parent dae4cfe8c5
commit 1afaa7b59d
2 changed files with 31 additions and 3 deletions

View File

@ -329,6 +329,7 @@ enum {
CO_RFL_KEEP_RECV = 0x0008, /* Instruct the mux to still wait for read events */ 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_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_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() */ /* flags that can be passed to xprt->snd_buf() and mux->snd_buf() */

View File

@ -330,9 +330,11 @@ static size_t raw_sock_to_buf(struct connection *conn, void *xprt_ctx, struct bu
goto read0; goto read0;
} }
if (!(fdtab[conn->handle.fd].state & FD_LINGER_RISK) || if (!(flags & CO_RFL_TRY_HARDER)) {
(cur_poller.flags & HAP_POLL_F_RDHUP)) { if (!(fdtab[conn->handle.fd].state & FD_LINGER_RISK) ||
break; (cur_poller.flags & HAP_POLL_F_RDHUP)) {
break;
}
} }
} }
count -= ret; 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) if (unlikely(conn->flags & CO_FL_WAIT_L4_CONN) && done)
conn->flags &= ~CO_FL_WAIT_L4_CONN; 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: leave:
return done; return done;