This commit is contained in:
Joshua Rogers 2026-07-30 18:18:33 +08:00 committed by GitHub
commit 8a49510e9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 33 additions and 5 deletions

View file

@ -92,7 +92,10 @@ send.
On failure, returns the appropriate error code.
This function may return **CURLE_AGAIN**. In this case, use your operating
system facilities to wait until the socket is writable, and retry.
system facilities to wait until the socket is writable, and retry. The
**buflen** passed to the retry should be no smaller than the one passed to
the call that returned **CURLE_AGAIN**, and **buffer** should hold the same
data.
If there is no socket available to use from the previous transfer, this
function returns **CURLE_UNSUPPORTED_PROTOCOL**.

View file

@ -87,6 +87,7 @@ struct mbed_ssl_backend_data {
const char *protocols[3];
#endif
int *ciphersuites;
const void *send_blocked_buf;
size_t send_blocked_len;
BIT(initialized); /* mbedtls_ssl_context is initialized */
BIT(sent_shutdown);
@ -1276,11 +1277,13 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data,
*pnwritten = 0;
connssl->io_need = CURL_SSL_IO_NEED_NONE;
/* mbedTLS is picky when a mbedtls_ssl_write() was previously blocked.
* It requires to be called with the same amount of bytes again, or it
* loses bytes, e.g. reporting all was sent but they were not.
* Remember the blocked length and use that when set. */
* It requires to be called with the same buffer and at least the same
* amount of bytes again, or it loses bytes, e.g. reporting all was sent
* but they were not. Remember the blocked buffer and length and use them
* when set. */
if(backend->send_blocked) {
DEBUGASSERT(backend->send_blocked_len <= len);
if(len < backend->send_blocked_len || mem != backend->send_blocked_buf)
return CURLE_SEND_ERROR;
CURL_TRC_CF(data, cf, "mbedtls_ssl_write(len=%zu) -> previously blocked "
"on %zu bytes", len, backend->send_blocked_len);
len = backend->send_blocked_len;
@ -1314,6 +1317,7 @@ static CURLcode mbed_send(struct Curl_cfilter *cf, struct Curl_easy *data,
if((result == CURLE_AGAIN) && !backend->send_blocked) {
backend->send_blocked = TRUE;
backend->send_blocked_len = len;
backend->send_blocked_buf = mem;
}
}

View file

@ -653,6 +653,27 @@ class TestUpload:
])
r.check_exit_code(0)
# issue #21689, h2 + mbedtls: a blocked mbedtls_ssl_write() must be
# retried with the same buffer; mbed_send() returns CURLE_SEND_ERROR
# otherwise. chunk_delay creates server-side backpressure so the
# kernel send buffer saturates and the partial-write resume path
# actually triggers on typical hosts.
@pytest.mark.skipif(condition=not Env.have_h2_curl(), reason="curl without h2")
def test_07_64_h2_mbedtls_blocked_send(self, env: Env, httpd, nghttpx):
if not env.curl_uses_lib('mbedtls'):
pytest.skip('test specific to mbedtls')
proto = 'h2'
fdata = os.path.join(env.gen_dir, 'data-10m')
curl = CurlClient(env=env)
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?'\
f'id=[0-0]&chunk_delay=2ms'
r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto,
extra_args=['--trace-config', 'ssl'])
r.check_stats(count=1, http_status=200, exitcode=0)
if not any('previously blocked' in line for line in r.trace_lines):
log.warning('mbedtls partial-write resume path not exercised; '
'test passed only because no backpressure occurred')
# nghttpx is the only server we have that supports TLS early data
@pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx")
@pytest.mark.parametrize("proto,upload_size", [