SSL_write partial data handling and WANT_WRITE limits
0 reputation · 25 Feb 2021, 16:03 UTC
0 reputation · 25 Feb 2021, 16:03 UTC
When implementing non-blocking I/O with OpenSSL, SSL_write may return a value indicating that only a portion of the provided buffer was processed. This typically occurs when the underlying transport layer is saturated, triggering an SSL_ERROR_WANT_WRITE return code.
The primary design constraint is ensuring data integrity by avoiding duplicate writes. Because the SSL object manages an internal buffer, the application must precisely track the number of bytes successfully accepted by the library to determine the correct offset for the subsequent retry attempt.
There is uncertainty regarding the boundary of 'written' data when utilizing custom BIO implementations. Specifically, it is unclear how the library distinguishes between data that has been buffered internally versus data that has been physically transmitted to the transport layer during a partial write.
SSL_ERROR_WANT_WRITE?34775 reputation · 25 Feb 2021, 21:53 UTC
When SSL_write returns a positive value less than the requested length and SSL_get_error yields SSL_ERROR_WANT_WRITE, that return value is the exact number of bytes the SSL object has already accepted from your buffer. The application must retry the write starting at buffer + ret.
OpenSSL tracks how many bytes have been successfully passed to the underlying BIO_write call. If the BIO’s write function consumes fewer bytes than requested (or returns -1 with SSL_ERROR_WANT_WRITE), the SSL layer counts the bytes that were actually consumed and returns that count. The offset therefore reflects the point up to which the input buffer has been read, regardless of whether those bytes are still buffered inside the SSL object or have been handed to the transport.
If a write is interrupted while a TLS record is being assembled, OpenSSL retains the incomplete record in its internal write buffer. The next call to SSL_write resumes from the beginning of that buffered partial record, preserving record integrity and preventing duplicate transmission of already‑sent bytes.
ret = SSL_write(ssl, buf, len);ret <= 0, obtain err = SSL_get_error(ssl, ret);err == SSL_ERROR_WANT_WRITE or err == SSL_ERROR_WANT_READ, retry from buf + ret with the remaining length len - ret.ret == len or a fatal error occurs.To confirm that your custom BIO behaves as expected, check that its write callback returns the number of bytes actually consumed, or -1 with SSL_ERROR_WANT_WRITE when it cannot accept more. If the callback does not report consumed bytes correctly, the offset tracking will be mismatched and you may need to adjust your retry logic.
You can observe this with OpenSSL’s built‑in debug callback:
BIO_set_callback(bio, BIO_debug_callback);
BIO_set_callback_arg(bio, (char *)"custom BIO");
Then run a minimal non‑blocking test that logs the return value of BIO_write alongside the SSL_write return and error.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 26 Feb 2021, 00:26 UTC
When SSL_write returns a value n and SSL_get_error reports SSL_ERROR_WANT_WRITE, the n is the number of bytes that the SSL layer has taken from your buffer into its internal write buffer. It does not guarantee that those bytes have already left the transport. The application must retry from buffer + n until n equals the total length requested.
Your BIO’s BIO_write must return the exact number of bytes it consumed, or -1 with BIO_should_retry(bio) true. If it returns 0 and BIO_should_retry is false, OpenSSL treats the connection as closed.
With SSL_MODE_ENABLE_PARTIAL_WRITE, OpenSSL will accept only part of the input even if the BIO could take more. SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER lets the caller change the buffer pointer between retries; otherwise the pointer must stay the same.