http2: avoid too early connection re-use/multiplexing

HTTP/1 connections that are upgraded to HTTP/2 should not be picked up
for reuse and multiplexing by other handles until the 101 switching
process is completed.

Lots-of-debgging-by: Stefan Eissing
Reported-by: Richard W.M. Jones
Bug: https://curl.se/mail/lib-2023-07/0045.html
Closes #11557
This commit is contained in:
Daniel Stenberg 2023-07-31 17:27:03 +02:00
parent 15c40a32b7
commit d4618a3fc4
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
5 changed files with 42 additions and 20 deletions

View File

@ -4058,6 +4058,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
/* Switching Protocols */
if(k->upgr101 == UPGR101_H2) {
/* Switching to HTTP/2 */
DEBUGASSERT(conn->httpversion < 20);
infof(data, "Received 101, Switching to HTTP/2");
k->upgr101 = UPGR101_RECEIVED;
@ -4100,6 +4101,11 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
}
}
else {
if(k->upgr101 == UPGR101_H2) {
/* A requested upgrade was denied, poke the multi handle to possibly
allow a pending pipewait to continue */
Curl_multi_connchanged(data->multi);
}
k->header = FALSE; /* no more header to parse! */
if((k->size == -1) && !k->chunk && !conn->bits.close &&

View File

@ -388,18 +388,6 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
static int error_callback(nghttp2_session *session, const char *msg,
size_t len, void *userp);
/*
* multi_connchanged() is called to tell that there is a connection in
* this multi handle that has changed state (multiplexing become possible, the
* number of allowed streams changed or similar), and a subsequent use of this
* multi handle should move CONNECT_PEND handles back to CONNECT to have them
* retry.
*/
static void multi_connchanged(struct Curl_multi *multi)
{
multi->recheckstate = TRUE;
}
/*
* Initialize the cfilter context
*/
@ -1120,7 +1108,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
/* only signal change if the value actually changed */
DEBUGF(LOG_CF(data, cf, "MAX_CONCURRENT_STREAMS now %u",
ctx->max_concurrent_streams));
multi_connchanged(data->multi);
Curl_multi_connchanged(data->multi);
}
/* Since the initial stream window is 64K, a request might be on HOLD,
* due to exhaustion. The (initial) SETTINGS may announce a much larger
@ -1148,7 +1136,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
ctx->goaway_error, ctx->last_stream_id));
infof(data, "received GOAWAY, error=%d, last_stream=%u",
ctx->goaway_error, ctx->last_stream_id);
multi_connchanged(data->multi);
Curl_multi_connchanged(data->multi);
}
break;
case NGHTTP2_WINDOW_UPDATE:
@ -1838,7 +1826,7 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
ssize_t nread = -1;
CURLcode result;
struct cf_call_data save;
DEBUGASSERT(stream);
CF_DATA_SAVE(save, cf, data);
nread = stream_recv(cf, data, buf, len, err);
@ -2601,7 +2589,7 @@ CURLcode Curl_http2_switch(struct Curl_easy *data,
conn->httpversion = 20; /* we know we're on HTTP/2 now */
conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
conn->bundle->multiuse = BUNDLE_MULTIPLEX;
multi_connchanged(data->multi);
Curl_multi_connchanged(data->multi);
if(cf->next) {
bool done;
@ -2629,7 +2617,7 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data)
cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */
cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
multi_connchanged(data->multi);
Curl_multi_connchanged(data->multi);
if(cf_h2->next) {
bool done;
@ -2686,7 +2674,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
conn->httpversion = 20; /* we know we're on HTTP/2 now */
conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
conn->bundle->multiuse = BUNDLE_MULTIPLEX;
multi_connchanged(data->multi);
Curl_multi_connchanged(data->multi);
if(cf->next) {
bool done;

View File

@ -1577,6 +1577,18 @@ static bool multi_ischanged(struct Curl_multi *multi, bool clear)
return retval;
}
/*
* Curl_multi_connchanged() is called to tell that there is a connection in
* this multi handle that has changed state (multiplexing become possible, the
* number of allowed streams changed or similar), and a subsequent use of this
* multi handle should move CONNECT_PEND handles back to CONNECT to have them
* retry.
*/
void Curl_multi_connchanged(struct Curl_multi *multi)
{
multi->recheckstate = TRUE;
}
CURLMcode Curl_multi_add_perform(struct Curl_multi *multi,
struct Curl_easy *data,
struct connectdata *conn)

View File

@ -41,6 +41,8 @@ void Curl_set_in_callback(struct Curl_easy *data, bool value);
bool Curl_is_in_callback(struct Curl_easy *easy);
CURLcode Curl_preconnect(struct Curl_easy *data);
void Curl_multi_connchanged(struct Curl_multi *multi);
/* Internal version of curl_multi_init() accepts size parameters for the
socket, connection and dns hashes */
struct Curl_multi *Curl_multi_handle(int hashsize, int chashsize,

View File

@ -1073,6 +1073,9 @@ ConnectionExists(struct Curl_easy *data,
bool wantProxyNTLMhttp = FALSE;
#endif
#endif
/* plain HTTP with upgrade */
bool h2upgrade = (data->state.httpwant == CURL_HTTP_VERSION_2_0) &&
(needle->handler->protocol & CURLPROTO_HTTP);
*force_reuse = FALSE;
*waitpipe = FALSE;
@ -1135,7 +1138,7 @@ ConnectionExists(struct Curl_easy *data,
}
if(data->set.ipver != CURL_IPRESOLVE_WHATEVER
&& data->set.ipver != check->ip_version) {
&& data->set.ipver != check->ip_version) {
/* skip because the connection is not via the requested IP version */
continue;
}
@ -1233,6 +1236,17 @@ ConnectionExists(struct Curl_easy *data,
}
#endif
if(h2upgrade && !check->httpversion && canmultiplex) {
if(data->set.pipewait) {
infof(data, "Server upgrade doesn't support multiplex yet, wait");
*waitpipe = TRUE;
CONNCACHE_UNLOCK(data);
return FALSE; /* no re-use */
}
infof(data, "Server upgrade cannot be used");
continue; /* can't be used atm */
}
if(!canmultiplex && CONN_INUSE(check))
/* this request can't be multiplexed but the checked connection is
already in use so we skip it */
@ -1289,7 +1303,7 @@ ConnectionExists(struct Curl_easy *data,
(((check->httpversion >= 20) &&
(data->state.httpwant < CURL_HTTP_VERSION_2_0))
|| ((check->httpversion >= 30) &&
(data->state.httpwant < CURL_HTTP_VERSION_3))))
(data->state.httpwant < CURL_HTTP_VERSION_3))))
continue;
#ifdef USE_SSH
else if(get_protocol_family(needle->handler) & PROTO_FAMILY_SSH) {