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); struct SSLsock *sslq = searchSSL(param->sostate, s);
if(!sslq) return 0; if(!sslq) return 0;
return ssl_pending(sslq->conn)? SPLICE_TLSPENDING : SPLICE_TLS; return ssl_buffered(sslq->conn)? SPLICE_TLSPENDING : SPLICE_TLS;
} }
#endif #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_read(SSL_CONN connection, void * buf, int bufsize);
int ssl_write(SSL_CONN connection, void * buf, int bufsize); int ssl_write(SSL_CONN connection, void * buf, int bufsize);
int ssl_pending(SSL_CONN connection); 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 /* 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 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); return SSL_write(conn->ssl, buf, bufsize);
} }
int ssl_pending(SSL_CONN connection) 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; ssl_conn *conn = (ssl_conn *) connection;