From 2514fb3dbf40faaccdd95cce0d4e772a1ad27056 Mon Sep 17 00:00:00 2001 From: Vladimir Dubrovin <3proxy@3proxy.ru> Date: Thu, 22 Oct 2026 12:05:02 +0300 Subject: [PATCH] TLS: a part of a record read ahead is not pending data SSL_has_pending() is true when only a part of a record has been read ahead, which is no data yet. ssl_poll() took it for data: poll() returned at once, over and over and without its timeout, until the rest came, so a peer sending half a record kept a thread spinning and its connection from timing out. ssl_pending() is now true only when decrypted data waits, or when what was read ahead makes a whole record (an SSL_peek() of one byte, the sockets not blocking); otherwise the socket is polled for the rest. splice() over kernel TLS still needs to know of any byte OpenSSL holds, a part of a record too: ssl_buffered() tells it. Co-Authored-By: Claude Opus 5.5 --- src/ssl.c | 2 +- src/ssl.h | 3 +++ src/ssllib.c | 24 ++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/ssl.c b/src/ssl.c index 4ff98f6..b64ceeb 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -429,7 +429,7 @@ static int ssl_splicetls(struct clientparam *param, SOCKET s){ struct SSLsock *sslq = searchSSL(param->sostate, s); if(!sslq) return 0; - return ssl_pending(sslq->conn)? SPLICE_TLSPENDING : SPLICE_TLS; + return ssl_buffered(sslq->conn)? SPLICE_TLSPENDING : SPLICE_TLS; } #endif diff --git a/src/ssl.h b/src/ssl.h index 4df3679..32cd19c 100644 --- a/src/ssl.h +++ b/src/ssl.h @@ -78,6 +78,9 @@ SSL_CONN ssl_handshake_to_server(SOCKET s, char * hostname, SSL_CONFIG *config, int ssl_read(SSL_CONN connection, void * buf, int bufsize); int ssl_write(SSL_CONN connection, void * buf, int bufsize); int ssl_pending(SSL_CONN connection); +/* any data read from the socket and not handed out, a part of a record + too */ +int ssl_buffered(SSL_CONN connection); /* TLS records are read ahead, a buffer at a time rather than two reads for each record, where what is read ahead and not yet decrypted shows as diff --git a/src/ssllib.c b/src/ssllib.c index a6be34e..e8e1bae 100644 --- a/src/ssllib.c +++ b/src/ssllib.c @@ -259,6 +259,30 @@ int ssl_write(SSL_CONN connection, void * buf, int bufsize) return SSL_write(conn->ssl, buf, bufsize); } int ssl_pending(SSL_CONN connection) +{ + ssl_conn *conn = (ssl_conn *) connection; +#if SSL_READAHEAD + char c; + int res; + + if(SSL_pending(conn->ssl) > 0) return 1; + if(!SSL_has_pending(conn->ssl)) return 0; + /* Read ahead and not decrypted: a record, or only a part of one. A + part is no data, and saying so would have poll() return at once, + again and again, without its timeout, until the rest came: it is + pending only once the record is whole (or the connection ended), + the socket polled for the rest otherwise. The sockets are not + blocking, so this does not wait. */ + res = SSL_peek(conn->ssl, &c, 1); + if(res > 0) return 1; + res = SSL_get_error(conn->ssl, res); + return res != SSL_ERROR_WANT_READ && res != SSL_ERROR_WANT_WRITE; +#else + return SSL_pending(conn->ssl); +#endif +} + +int ssl_buffered(SSL_CONN connection) { ssl_conn *conn = (ssl_conn *) connection;