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

166
lib/url.c
View file

@ -452,7 +452,7 @@ CURLcode Curl_close(struct Curl_easy **datap)
Curl_resolver_cancel(data);
Curl_resolver_cleanup(data->state.async.resolver);
Curl_http2_cleanup_dependencies(data);
Curl_data_priority_cleanup(data);
/* No longer a dirty share, if it exists */
if(data->share) {
@ -640,14 +640,15 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
set->maxage_conn = 118;
set->maxlifetime_conn = 0;
set->http09_allowed = FALSE;
set->httpwant =
#ifdef USE_HTTP2
CURL_HTTP_VERSION_2TLS
set->httpwant = CURL_HTTP_VERSION_2TLS
#else
CURL_HTTP_VERSION_1_1
set->httpwant = CURL_HTTP_VERSION_1_1
#endif
;
Curl_http2_init_userset(set);
#if defined(USE_HTTP2) || defined(USE_HTTP3)
memset(&set->priority, 0, sizeof(set->priority));
#endif
set->quick_exit = 0L;
return result;
}
@ -877,24 +878,6 @@ void Curl_disconnect(struct Curl_easy *data,
conn_free(data, conn);
}
/*
* This function should return TRUE if the socket is to be assumed to
* be dead. Most commonly this happens when the server has closed the
* connection due to inactivity.
*/
static bool SocketIsDead(curl_socket_t sock)
{
int sval;
bool ret_val = TRUE;
sval = SOCKET_READABLE(sock, 0);
if(sval == 0)
/* timeout */
ret_val = FALSE;
return ret_val;
}
/*
* IsMultiplexingPossible()
*
@ -1025,8 +1008,7 @@ static bool extract_if_dead(struct connectdata *conn,
}
else {
/* Use the general method for determining the death of a connection */
dead = SocketIsDead(conn->sock[FIRSTSOCKET]);
dead = !Curl_conn_is_alive(data, conn);
}
if(dead) {
@ -1355,8 +1337,10 @@ ConnectionExists(struct Curl_easy *data,
/* If multiplexing isn't enabled on the h2 connection and h1 is
explicitly requested, handle it: */
if((needle->handler->protocol & PROTO_FAMILY_HTTP) &&
(check->httpversion >= 20) &&
(data->state.httpwant < CURL_HTTP_VERSION_2_0))
(((check->httpversion >= 20) &&
(data->state.httpwant < CURL_HTTP_VERSION_2_0))
|| ((check->httpversion >= 30) &&
(data->state.httpwant < CURL_HTTP_VERSION_3))))
continue;
if(get_protocol_family(needle->handler) == PROTO_FAMILY_SSH) {
@ -1478,9 +1462,8 @@ ConnectionExists(struct Curl_easy *data,
#ifdef USE_NGHTTP2
/* If multiplexed, make sure we don't go over concurrency limit */
if(check->bits.multiplex) {
/* Multiplexed connections can only be HTTP/2 for now */
struct http_conn *httpc = &check->proto.httpc;
if(multiplexed >= httpc->settings.max_concurrent_streams) {
if(multiplexed >= Curl_conn_get_max_concurrent(data, check,
FIRSTSOCKET)) {
infof(data, "MAX_CONCURRENT_STREAMS reached, skip (%zu)",
multiplexed);
continue;
@ -1562,8 +1545,6 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */
conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */
conn->tempsock[0] = CURL_SOCKET_BAD; /* no file descriptor */
conn->tempsock[1] = CURL_SOCKET_BAD; /* no file descriptor */
conn->connection_id = -1; /* no ID */
conn->port = -1; /* unknown at this point */
conn->remote_port = -1; /* unknown at this point */
@ -3356,12 +3337,6 @@ static void reuse_conn(struct Curl_easy *data,
struct connectdata *temp,
struct connectdata *existing)
{
/* 'local_ip' and 'local_port' get filled with local's numerical
ip address and port number whenever an outgoing connection is
**established** from the primary socket to a remote address. */
char local_ip[MAX_IPADR_LEN] = "";
int local_port = -1;
/* get the user+password information from the temp struct since it may
* be new for this request even when we re-use an existing connection */
if(temp->user) {
@ -3409,12 +3384,8 @@ static void reuse_conn(struct Curl_easy *data,
existing->hostname_resolve = temp->hostname_resolve;
temp->hostname_resolve = NULL;
/* persist connection info in session handle */
if(existing->transport == TRNSPRT_TCP) {
Curl_conninfo_local(data, existing->sock[FIRSTSOCKET],
local_ip, &local_port);
}
Curl_persistconninfo(data, existing, local_ip, local_port);
/* persist existing connection info in data */
Curl_conn_ev_update_info(data, existing);
conn_reset_all_postponed_data(temp); /* free buffers */
@ -3891,6 +3862,13 @@ static CURLcode create_conn(struct Curl_easy *data,
* Resolve the address of the server or proxy
*************************************************************/
result = resolve_server(data, conn, async);
if(result)
goto out;
/* Everything general done, inform filters that they need
* to prepare for a data transfer.
*/
result = Curl_conn_ev_data_setup(data);
out:
return result;
@ -4029,3 +4007,103 @@ CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn)
return CURLE_OK;
}
#if defined(USE_HTTP2) || defined(USE_HTTP3)
#ifdef USE_NGHTTP2
static void priority_remove_child(struct Curl_easy *parent,
struct Curl_easy *child)
{
struct Curl_data_prio_node **pnext = &parent->set.priority.children;
struct Curl_data_prio_node *pnode = parent->set.priority.children;
DEBUGASSERT(child->set.priority.parent == parent);
while(pnode && pnode->data != child) {
pnext = &pnode->next;
pnode = pnode->next;
}
DEBUGASSERT(pnode);
if(pnode) {
*pnext = pnode->next;
free(pnode);
}
child->set.priority.parent = 0;
child->set.priority.exclusive = FALSE;
}
CURLcode Curl_data_priority_add_child(struct Curl_easy *parent,
struct Curl_easy *child,
bool exclusive)
{
if(child->set.priority.parent) {
priority_remove_child(child->set.priority.parent, child);
}
if(parent) {
struct Curl_data_prio_node **tail;
struct Curl_data_prio_node *pnode;
pnode = calloc(1, sizeof(*pnode));
if(!pnode)
return CURLE_OUT_OF_MEMORY;
pnode->data = child;
if(parent->set.priority.children && exclusive) {
/* exclusive: move all existing children underneath the new child */
struct Curl_data_prio_node *node = parent->set.priority.children;
while(node) {
node->data->set.priority.parent = child;
node = node->next;
}
tail = &child->set.priority.children;
while(*tail)
tail = &(*tail)->next;
DEBUGASSERT(!*tail);
*tail = parent->set.priority.children;
parent->set.priority.children = 0;
}
tail = &parent->set.priority.children;
while(*tail) {
(*tail)->data->set.priority.exclusive = FALSE;
tail = &(*tail)->next;
}
DEBUGASSERT(!*tail);
*tail = pnode;
}
child->set.priority.parent = parent;
child->set.priority.exclusive = exclusive;
return CURLE_OK;
}
#endif /* USE_NGHTTP2 */
void Curl_data_priority_cleanup(struct Curl_easy *data)
{
#ifdef USE_NGHTTP2
while(data->set.priority.children) {
struct Curl_easy *tmp = data->set.priority.children->data;
priority_remove_child(data, tmp);
if(data->set.priority.parent)
Curl_data_priority_add_child(data->set.priority.parent, tmp, FALSE);
}
if(data->set.priority.parent)
priority_remove_child(data->set.priority.parent, data);
#endif
(void)data;
}
void Curl_data_priority_clear_state(struct Curl_easy *data)
{
memset(&data->state.priority, 0, sizeof(data->state.priority));
}
#endif /* defined(USE_HTTP2) || defined(USE_HTTP3) */