lib: connect/h2/h3 refactor

Refactoring of connection setup and happy eyeballing. Move
nghttp2. ngtcp2, quiche and msh3 into connection filters.

 - eyeballing cfilter that uses sub-filters for performing parallel connects
 - socket cfilter for all transport types, including QUIC
 - QUIC implementations in cfilter, can now participate in eyeballing
 - connection setup is more dynamic in order to adapt to what filter did
   really connect.  Relevant to see if a SSL filter needs to be added or
   if SSL has already been provided
 - HTTP/3 test cases similar to HTTP/2
 - multiuse of parallel transfers for HTTP/3, tested for ngtcp2 and quiche

 - Fix for data attach/detach in VTLS filters that could lead to crashes
   during parallel transfers.
 - Eliminating setup() methods in cfilters, no longer needed.
 - Improving Curl_conn_is_alive() to replace Curl_connalive() and
   integrated ssl alive checks into cfilter.
 - Adding CF_CNTRL_CONN_INFO_UPDATE to tell filters to update
   connection into and persist it at the easy handle.

 - Several more cfilter related cleanups and moves:
   - stream_weigth and dependency info is now wrapped in struct
     Curl_data_priority
   - Curl_data_priority members depend is available in HTTP2|HTTP3
   - Curl_data_priority members depend on NGHTTP2 support
   - handling init/reset/cleanup of priority part of url.c
   - data->state.priority same struct, but shallow copy for compares only

 - PROTOPT_STREAM has been removed
   - Curl_conn_is_mulitplex() now available to check on capability

 - Adding query method to connection filters.
   - ngtcp2+quiche: implementing query for max concurrent transfers.

 - Adding is_alive and keep_alive cfilter methods. Adding DATA_SETUP event.
   - setting keepalive timestamp on connect
   - DATA_SETUP is called after the connection has been completely
     setup (but may not connected yet) to allow filters to initialize
     data members they use.

 - there is no socket to be had with msh3, it is unclear how select
   shall work

 - manual test via "curl --http3 https://curl.se" fail with "empty
   reply from server".

 - Various socket/conn related cleanups:
   - Curl_socket is now Curl_socket_open and in cf-socket.c
   - Curl_closesocket is now Curl_socket_close and in cf-socket.c
   - Curl_ssl_use has been replaced with Cur_conn_is_ssl
   - Curl_conn_tcp_accepted_set has been split into
     Curl_conn_tcp_listen_set and Curl_conn_tcp_accepted_set
     with a clearer purpose

Closes #10141
This commit is contained in:
Stefan Eissing 2022-12-30 09:14:55 +01:00 committed by Daniel Stenberg
parent 1c18f8da51
commit 71b7e01610
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
48 changed files with 6908 additions and 4675 deletions

View file

@ -73,6 +73,7 @@
#include "url.h"
#include "getinfo.h"
#include "vtls/vtls.h"
#include "vquic/vquic.h"
#include "select.h"
#include "multiif.h"
#include "connect.h"
@ -367,27 +368,12 @@ static int data_pending(struct Curl_easy *data)
{
struct connectdata *conn = data->conn;
#ifdef ENABLE_QUIC
if(conn->transport == TRNSPRT_QUIC)
return Curl_quic_data_pending(data);
#endif
if(conn->handler->protocol&PROTO_FAMILY_FTP)
return Curl_conn_data_pending(data, SECONDARYSOCKET);
/* in the case of libssh2, we can never be really sure that we have emptied
its internal buffers so we MUST always try until we get EAGAIN back */
return conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP) ||
#ifdef USE_NGHTTP2
/* For HTTP/2, we may read up everything including response body
with header fields in Curl_http_readwrite_headers. If no
content-length is provided, curl waits for the connection
close, which we emulate it using conn->proto.httpc.closed =
TRUE. The thing is if we read everything, then http2_recv won't
be called and we cannot signal the HTTP/2 stream has closed. As
a workaround, we return nonzero here to call http2_recv. */
((conn->handler->protocol&PROTO_FAMILY_HTTP) && conn->httpversion >= 20) ||
#endif
Curl_conn_data_pending(data, FIRSTSOCKET);
}
@ -454,29 +440,16 @@ static CURLcode readwrite_data(struct Curl_easy *data,
bool is_empty_data = FALSE;
size_t buffersize = data->set.buffer_size;
size_t bytestoread = buffersize;
#ifdef USE_NGHTTP2
bool is_http2 = ((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
(conn->httpversion == 20));
#endif
bool is_http3 =
#ifdef ENABLE_QUIC
((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
(conn->httpversion == 30));
#else
FALSE;
#endif
/* For HTTP/2 and HTTP/3, read data without caring about the content
length. This is safe because body in HTTP/2 is always segmented
thanks to its framing layer. Meanwhile, we have to call Curl_read
to ensure that http2_handle_stream_close is called when we read all
incoming bytes for a particular stream. */
bool is_http3 = Curl_conn_is_http3(data, conn, FIRSTSOCKET);
bool data_eof_handled = is_http3
|| Curl_conn_is_http2(data, conn, FIRSTSOCKET);
if(
#ifdef USE_NGHTTP2
/* For HTTP/2, read data without caring about the content length. This
is safe because body in HTTP/2 is always segmented thanks to its
framing layer. Meanwhile, we have to call Curl_read to ensure that
http2_handle_stream_close is called when we read all incoming bytes
for a particular stream. */
!is_http2 &&
#endif
!is_http3 && /* Same reason mentioned above. */
k->size != -1 && !k->header) {
if(!data_eof_handled && k->size != -1 && !k->header) {
/* make sure we don't read too much */
curl_off_t totalleft = k->size - k->bytecount;
if(totalleft < (curl_off_t)bytestoread)
@ -499,7 +472,7 @@ static CURLcode readwrite_data(struct Curl_easy *data,
else {
/* read nothing but since we wanted nothing we consider this an OK
situation to proceed from */
DEBUGF(infof(data, "readwrite_data: we're done"));
DEBUGF(infof(data, DMSG(data, "readwrite_data: we're done")));
nread = 0;
}
@ -518,14 +491,9 @@ static CURLcode readwrite_data(struct Curl_easy *data,
buf[nread] = 0;
}
else {
/* if we receive 0 or less here, either the http2 stream is closed or the
/* if we receive 0 or less here, either the data transfer is done or the
server closed the connection and we bail out from this! */
#ifdef USE_NGHTTP2
if(is_http2 && !nread)
DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
else
#endif
if(is_http3 && !nread)
if(data_eof_handled)
DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
else
DEBUGF(infof(data, "nread <= 0, server closed connection, bailing"));
@ -799,19 +767,18 @@ static CURLcode readwrite_data(struct Curl_easy *data,
}
out:
DEBUGF(infof(data, "readwrite_data(handle=%p) -> %d", data, result));
if(result)
DEBUGF(infof(data, DMSG(data, "readwrite_data() -> %d"), result));
return result;
}
CURLcode Curl_done_sending(struct Curl_easy *data,
struct SingleRequest *k)
{
struct connectdata *conn = data->conn;
k->keepon &= ~KEEP_SEND; /* we're done writing */
/* These functions should be moved into the handler struct! */
Curl_http2_done_sending(data, conn);
Curl_quic_done_sending(data);
Curl_conn_ev_data_done_send(data);
return CURLE_OK;
}
@ -1182,13 +1149,9 @@ CURLcode Curl_readwrite(struct connectdata *conn,
}
}
#ifdef ENABLE_QUIC
if(conn->transport == TRNSPRT_QUIC) {
result = Curl_quic_idle(data);
if(result)
goto out;
}
#endif
result = Curl_conn_ev_data_idle(data);
if(result)
goto out;
}
if(Curl_pgrsUpdate(data))
@ -1264,7 +1227,8 @@ CURLcode Curl_readwrite(struct connectdata *conn,
KEEP_RECV_PAUSE|KEEP_SEND_PAUSE))) ? TRUE : FALSE;
result = CURLE_OK;
out:
DEBUGF(infof(data, "Curl_readwrite(handle=%p) -> %d", data, result));
if(result)
DEBUGF(infof(data, DMSG(data, "Curl_readwrite() -> %d"), result));
return result;
}
@ -1377,6 +1341,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
data->state.authhost.want = data->set.httpauth;
data->state.authproxy.want = data->set.proxyauth;
Curl_safefree(data->info.wouldredirect);
Curl_data_priority_clear_state(data);
if(data->state.httpreq == HTTPREQ_PUT)
data->state.infilesize = data->set.filesize;
@ -1434,7 +1399,6 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
}
}
#endif
Curl_http2_init_state(&data->state);
result = Curl_hsts_loadcb(data, data->hsts);
}
@ -1872,7 +1836,7 @@ Curl_setup_transfer(
httpsending = ((conn->handler->protocol&PROTO_FAMILY_HTTP) &&
(http->sending == HTTPSEND_REQUEST));
if(conn->bits.multiplex || conn->httpversion == 20 || httpsending) {
if(conn->bits.multiplex || conn->httpversion >= 20 || httpsending) {
/* when multiplexing, the read/write sockets need to be the same! */
conn->sockfd = sockindex == -1 ?
((writesockindex == -1 ? CURL_SOCKET_BAD : conn->sock[writesockindex])) :