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 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-22 12:05:02 +03:00
parent 3d20e1104e
commit 2514fb3dbf
3 changed files with 28 additions and 1 deletions

View File

@ -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

View File

@ -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

View File

@ -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;