}
/*
- * Given one filled in connection struct (named needle), this function should
- * detect if there already is one that has all the significant details
- * exactly the same and thus should be used instead.
+ * Given a transfer and a prototype connection (needle),
+ * find and attach an existing connection that matches.
*
- * If there is a match, this function returns TRUE - and has marked the
- * connection as 'in-use'. It must later be called with ConnectionDone() to
- * return back to 'idle' (unused) state.
- *
- * The force_reuse flag is set if the connection must be used.
+ * Return TRUE if an existing connection was attached.
+ * `waitpipe` is TRUE if no existing connection matched, but there
+ * might be suitable one in the near future (common cause: multiplexing
+ * capability has not been determined yet, e.g. ALPN handshake).
*/
-static bool ConnectionExists(struct Curl_easy *data,
- struct connectdata *needle,
- struct connectdata **usethis,
- bool *force_reuse,
- bool *waitpipe)
+static bool url_attach_existing(struct Curl_easy *data,
+ struct connectdata *needle,
+ bool *waitpipe)
{
struct url_conn_match match;
bool result;
+ DEBUGASSERT(!data->conn);
memset(&match, 0, sizeof(match));
match.data = data;
match.needle = needle;
/* wait_pipe is TRUE if we encounter a bundle that is undecided. There
* is no matching connection then, yet. */
- *usethis = match.found;
- *force_reuse = (bool)match.force_reuse;
*waitpipe = (bool)match.wait_pipe;
return result;
}
}
/*
- * Cleanup the connection `temp`, just allocated for `data`, before using the
- * previously `existing` one for `data`. All relevant info is copied over
- * and `temp` is freed.
+ * Adjust reused connection settings to the transfer/needle.
*/
-static void reuse_conn(struct Curl_easy *data,
- struct connectdata *temp,
- struct connectdata *existing)
+static void url_conn_reuse_adjust(struct Curl_easy *data,
+ struct connectdata *needle)
{
- /* get the user+password information from the temp struct since it may
- * be new for this request even when we reuse an existing connection */
- if(temp->user) {
+ struct connectdata *conn = data->conn;
+
+ /* get the user+password information from the needle since it may
+ * be new for this request even when we reuse conn */
+ if(needle->user) {
/* use the new username and password though */
- curlx_free(existing->user);
- curlx_free(existing->passwd);
- existing->user = temp->user;
- existing->passwd = temp->passwd;
- temp->user = NULL;
- temp->passwd = NULL;
+ curlx_free(conn->user);
+ curlx_free(conn->passwd);
+ conn->user = needle->user;
+ conn->passwd = needle->passwd;
+ needle->user = NULL;
+ needle->passwd = NULL;
}
#ifndef CURL_DISABLE_PROXY
- existing->bits.proxy_user_passwd = temp->bits.proxy_user_passwd;
- if(existing->bits.proxy_user_passwd) {
+ conn->bits.proxy_user_passwd = needle->bits.proxy_user_passwd;
+ if(conn->bits.proxy_user_passwd) {
/* use the new proxy username and proxy password though */
- curlx_free(existing->http_proxy.user);
- curlx_free(existing->socks_proxy.user);
- curlx_free(existing->http_proxy.passwd);
- curlx_free(existing->socks_proxy.passwd);
- existing->http_proxy.user = temp->http_proxy.user;
- existing->socks_proxy.user = temp->socks_proxy.user;
- existing->http_proxy.passwd = temp->http_proxy.passwd;
- existing->socks_proxy.passwd = temp->socks_proxy.passwd;
- temp->http_proxy.user = NULL;
- temp->socks_proxy.user = NULL;
- temp->http_proxy.passwd = NULL;
- temp->socks_proxy.passwd = NULL;
+ curlx_free(conn->http_proxy.user);
+ curlx_free(conn->socks_proxy.user);
+ curlx_free(conn->http_proxy.passwd);
+ curlx_free(conn->socks_proxy.passwd);
+ conn->http_proxy.user = needle->http_proxy.user;
+ conn->socks_proxy.user = needle->socks_proxy.user;
+ conn->http_proxy.passwd = needle->http_proxy.passwd;
+ conn->socks_proxy.passwd = needle->socks_proxy.passwd;
+ needle->http_proxy.user = NULL;
+ needle->socks_proxy.user = NULL;
+ needle->http_proxy.passwd = NULL;
+ needle->socks_proxy.passwd = NULL;
}
#endif
* - we use a proxy (not tunneling). we want to send all requests
* that use the same proxy on this connection.
* - we have a "connect-to" setting that may redirect the hostname of
- * a new request to the same remote endpoint of an existing conn.
- * We want to reuse an existing conn to the remote endpoint.
+ * a new request to the same remote endpoint of an conn conn.
+ * We want to reuse an conn conn to the remote endpoint.
* Since connection reuse does not match on conn->host necessarily, we
- * switch `existing` conn to `temp` conn's host settings.
- * Is this correct in the case of TLS connections that have
- * used the original hostname in SNI to negotiate? Do we send
- * requests for another host through the different SNI?
+ * switch conn to needle's host settings.
*/
- url_move_hostname(&existing->host, &temp->host);
- url_move_hostname(&existing->conn_to_host, &temp->conn_to_host);
+ url_move_hostname(&conn->host, &needle->host);
+ url_move_hostname(&conn->conn_to_host, &needle->conn_to_host);
- existing->conn_to_port = temp->conn_to_port;
- existing->remote_port = temp->remote_port;
- curlx_free(existing->hostname_resolve);
- existing->hostname_resolve = temp->hostname_resolve;
- temp->hostname_resolve = NULL;
-
- /* reuse init */
- existing->bits.reuse = TRUE; /* yes, we are reusing here */
-
- Curl_conn_free(data, temp);
+ conn->conn_to_port = needle->conn_to_port;
+ conn->remote_port = needle->remote_port;
+ curlx_free(conn->hostname_resolve);
+ conn->hostname_resolve = needle->hostname_resolve;
+ needle->hostname_resolve = NULL;
}
static void conn_meta_freeentry(void *p)
DEBUGASSERT(p == NULL);
}
-/**
- * create_conn() sets up a new connectdata struct, or reuses an already
- * existing one, and resolves hostname.
- *
- * if this function returns CURLE_OK and *async is set to TRUE, the resolve
- * response will be coming asynchronously. If *async is FALSE, the name is
- * already resolved.
- *
- * @param data The sessionhandle pointer
- * @param in_connect is set to the next connection data pointer
- * @param reusedp is set to to TRUE if connection was reused
- * @see Curl_setup_conn()
- *
- */
-
-static CURLcode create_conn(struct Curl_easy *data,
- struct connectdata **in_connect,
- bool *reusedp)
+static CURLcode url_create_needle(struct Curl_easy *data,
+ struct connectdata **pneedle)
{
+ struct connectdata *needle = NULL;
CURLcode result = CURLE_OK;
- struct connectdata *conn;
- struct connectdata *existing = NULL;
- bool reuse;
- bool connections_available = TRUE;
- bool force_reuse = FALSE;
- bool waitpipe = FALSE;
-
- *reusedp = FALSE;
- *in_connect = NULL;
/*************************************************************
* Check input data
parts for checking against the already present connections. In order
to not have to modify everything at once, we allocate a temporary
connection data struct and fill in for comparison purposes. */
- conn = allocate_conn(data);
-
- if(!conn) {
+ needle = allocate_conn(data);
+ if(!needle) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
- /* We must set the return variable as soon as possible, so that our
- parent can cleanup any possible allocs we may have done before
- any failure */
- *in_connect = conn;
-
/* Do the unfailable inits first, before checks that may early return */
- Curl_hash_init(&conn->meta_hash, 23,
+ Curl_hash_init(&needle->meta_hash, 23,
Curl_hash_str, curlx_str_key_compare, conn_meta_freeentry);
- result = parseurlandfillconn(data, conn);
+ result = parseurlandfillconn(data, needle);
if(result)
goto out;
if(data->set.str[STRING_SASL_AUTHZID]) {
- conn->sasl_authzid = curlx_strdup(data->set.str[STRING_SASL_AUTHZID]);
- if(!conn->sasl_authzid) {
+ needle->sasl_authzid = curlx_strdup(data->set.str[STRING_SASL_AUTHZID]);
+ if(!needle->sasl_authzid) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
}
if(data->set.str[STRING_BEARER]) {
- conn->oauth_bearer = curlx_strdup(data->set.str[STRING_BEARER]);
- if(!conn->oauth_bearer) {
+ needle->oauth_bearer = curlx_strdup(data->set.str[STRING_BEARER]);
+ if(!needle->oauth_bearer) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
#ifdef USE_UNIX_SOCKETS
if(data->set.str[STRING_UNIX_SOCKET_PATH]) {
- conn->unix_domain_socket =
+ needle->unix_domain_socket =
curlx_strdup(data->set.str[STRING_UNIX_SOCKET_PATH]);
- if(!conn->unix_domain_socket) {
+ if(!needle->unix_domain_socket) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
- conn->bits.abstract_unix_socket = data->set.abstract_unix_socket;
+ needle->bits.abstract_unix_socket = data->set.abstract_unix_socket;
}
#endif
/* After the Unix socket init but before the proxy vars are used, parse and
initialize the proxy vars */
#ifndef CURL_DISABLE_PROXY
- result = create_conn_helper_init_proxy(data, conn);
+ result = create_conn_helper_init_proxy(data, needle);
if(result)
goto out;
* If the protocol is using SSL and HTTP proxy is used, we set
* the tunnel_proxy bit.
*************************************************************/
- if((conn->given->flags & PROTOPT_SSL) && conn->bits.httpproxy)
- conn->bits.tunnel_proxy = TRUE;
+ if((needle->given->flags & PROTOPT_SSL) && needle->bits.httpproxy)
+ needle->bits.tunnel_proxy = TRUE;
#endif
/*************************************************************
* Figure out the remote port number and fix it in the URL
*************************************************************/
- result = parse_remote_port(data, conn);
+ result = parse_remote_port(data, needle);
if(result)
goto out;
/* Check for overridden login details and set them accordingly so that
they are known when protocol->setup_connection is called! */
- result = override_login(data, conn);
+ result = override_login(data, needle);
if(result)
goto out;
- result = set_login(data, conn); /* default credentials */
+ result = set_login(data, needle); /* default credentials */
if(result)
goto out;
* Process the "connect to" linked list of hostname/port mappings.
* Do this after the remote port number has been fixed in the URL.
*************************************************************/
- result = parse_connect_to_slist(data, conn, data->set.connect_to);
+ result = parse_connect_to_slist(data, needle, data->set.connect_to);
if(result)
goto out;
* IDN-convert the proxy hostnames
*************************************************************/
#ifndef CURL_DISABLE_PROXY
- if(conn->bits.httpproxy) {
- result = Curl_idnconvert_hostname(&conn->http_proxy.host);
+ if(needle->bits.httpproxy) {
+ result = Curl_idnconvert_hostname(&needle->http_proxy.host);
if(result)
- return result;
+ goto out;
}
- if(conn->bits.socksproxy) {
- result = Curl_idnconvert_hostname(&conn->socks_proxy.host);
+ if(needle->bits.socksproxy) {
+ result = Curl_idnconvert_hostname(&needle->socks_proxy.host);
if(result)
- return result;
+ goto out;
}
#endif
- if(conn->bits.conn_to_host) {
- result = Curl_idnconvert_hostname(&conn->conn_to_host);
+ if(needle->bits.conn_to_host) {
+ result = Curl_idnconvert_hostname(&needle->conn_to_host);
if(result)
- return result;
+ goto out;
}
/*************************************************************
* Check whether the host and the "connect to host" are equal.
* Do this after the hostnames have been IDN-converted.
*************************************************************/
- if(conn->bits.conn_to_host &&
- curl_strequal(conn->conn_to_host.name, conn->host.name)) {
- conn->bits.conn_to_host = FALSE;
+ if(needle->bits.conn_to_host &&
+ curl_strequal(needle->conn_to_host.name, needle->host.name)) {
+ needle->bits.conn_to_host = FALSE;
}
/*************************************************************
* Check whether the port and the "connect to port" are equal.
* Do this after the remote port number has been fixed in the URL.
*************************************************************/
- if(conn->bits.conn_to_port && conn->conn_to_port == conn->remote_port) {
- conn->bits.conn_to_port = FALSE;
+ if(needle->bits.conn_to_port &&
+ needle->conn_to_port == needle->remote_port) {
+ needle->bits.conn_to_port = FALSE;
}
#ifndef CURL_DISABLE_PROXY
* If the "connect to" feature is used with an HTTP proxy,
* we set the tunnel_proxy bit.
*************************************************************/
- if((conn->bits.conn_to_host || conn->bits.conn_to_port) &&
- conn->bits.httpproxy)
- conn->bits.tunnel_proxy = TRUE;
+ if((needle->bits.conn_to_host || needle->bits.conn_to_port) &&
+ needle->bits.httpproxy)
+ needle->bits.tunnel_proxy = TRUE;
#endif
/*************************************************************
* Setup internals depending on protocol. Needs to be done after
* we figured out what/if proxy to use.
*************************************************************/
- result = setup_connection_internals(data, conn);
+ result = setup_connection_internals(data, needle);
+ if(result)
+ goto out;
+
+ if(needle->scheme->flags & PROTOPT_ALPN) {
+ /* The protocol wants it, so set the bits if enabled in the easy handle
+ (default) */
+ if(data->set.ssl_enable_alpn)
+ needle->bits.tls_enable_alpn = TRUE;
+ }
+
+ if(!(needle->scheme->flags & PROTOPT_NONETWORK)) {
+ /* Setup callbacks for network connections */
+ needle->recv[FIRSTSOCKET] = Curl_cf_recv;
+ needle->send[FIRSTSOCKET] = Curl_cf_send;
+ needle->recv[SECONDARYSOCKET] = Curl_cf_recv;
+ needle->send[SECONDARYSOCKET] = Curl_cf_send;
+ needle->bits.tcp_fastopen = data->set.tcp_fastopen;
+ }
+
+out:
+ if(!result) {
+ DEBUGASSERT(needle);
+ *pneedle = needle;
+ }
+ else {
+ *pneedle = NULL;
+ if(needle)
+ Curl_conn_free(data, needle);
+ }
+ return result;
+}
+
+/**
+ * Find an existing connection for the transfer or create a new one.
+ * Returns
+ * - CURLE_OK on success with a connection attached to data
+ * - CURLE_NO_CONNECTION_AVAILABLE when connection limits apply or when
+ * a suitable connection has not determined its multiplex capability.
+ * - a fatal error
+ */
+static CURLcode url_find_or_create_conn(struct Curl_easy *data)
+{
+ struct connectdata *needle = NULL;
+ bool waitpipe = FALSE;
+ CURLcode result;
+
+ /* create the template connection for transfer data. Use this needle to
+ * find an existing connection or, if none exists, convert needle
+ * to a full connection and attach it to data. */
+ result = url_create_needle(data, &needle);
if(result)
goto out;
+ DEBUGASSERT(needle);
/***********************************************************************
* file: is a special case in that it does not need a network connection
***********************************************************************/
#ifndef CURL_DISABLE_FILE
- if(conn->scheme->flags & PROTOPT_NONETWORK) {
+ if(needle->scheme->flags & PROTOPT_NONETWORK) {
bool done;
/* this is supposed to be the connect function so we better at least check
that the file is present here! */
- DEBUGASSERT(conn->scheme->run->connect_it);
- data->info.conn_scheme = conn->scheme->name;
+ DEBUGASSERT(needle->scheme->run->connect_it);
+ data->info.conn_scheme = needle->scheme->name;
/* conn_protocol can only provide "old" protocols */
- data->info.conn_protocol = (conn->scheme->protocol) & CURLPROTO_MASK;
- result = conn->scheme->run->connect_it(data, &done);
+ data->info.conn_protocol = (needle->scheme->protocol) & CURLPROTO_MASK;
+ result = needle->scheme->run->connect_it(data, &done);
if(result)
goto out;
/* Setup a "faked" transfer that will do nothing */
- Curl_attach_connection(data, conn);
- result = Curl_cpool_add(data, conn);
+ Curl_attach_connection(data, needle);
+ needle = NULL;
+ result = Curl_cpool_add(data, data->conn);
if(!result) {
/* Setup whatever necessary for a resumed transfer */
result = setup_range(data);
if(!result) {
Curl_xfer_setup_nop(data);
- result = Curl_init_do(data, conn);
+ result = Curl_init_do(data, data->conn);
}
}
if(result) {
- DEBUGASSERT(conn->scheme->run->done);
+ DEBUGASSERT(data->conn->scheme->run->done);
/* we ignore the return code for the protocol-specific DONE */
- (void)conn->scheme->run->done(data, result, FALSE);
+ (void)data->conn->scheme->run->done(data, result, FALSE);
}
goto out;
}
#endif
- /* Setup filter for network connections */
- conn->recv[FIRSTSOCKET] = Curl_cf_recv;
- conn->send[FIRSTSOCKET] = Curl_cf_send;
- conn->recv[SECONDARYSOCKET] = Curl_cf_recv;
- conn->send[SECONDARYSOCKET] = Curl_cf_send;
- conn->bits.tcp_fastopen = data->set.tcp_fastopen;
-
/* Complete the easy's SSL configuration for connection cache matching */
result = Curl_ssl_easy_config_complete(data);
if(result)
goto out;
+ /* Get rid of any dead connections so limit are easier kept. */
Curl_cpool_prune_dead(data);
/*************************************************************
- * Check the current list of connections to see if we can
- * reuse an already existing one or if we have to create a
- * new one.
+ * Reuse of existing connection is not allowed when
+ * - connect_only is set or
+ * - reuse_fresh is set and this is not a follow-up request
+ * (like with HTTP followlocation)
*************************************************************/
+ if((!data->set.reuse_fresh || data->state.followlocation) &&
+ !data->set.connect_only) {
+ /* Ok, try to find and attach an existing one */
+ url_attach_existing(data, needle, &waitpipe);
+ }
- DEBUGASSERT(conn->user);
- DEBUGASSERT(conn->passwd);
-
- /* reuse_fresh is TRUE if we are told to use a new connection by force, but
- we only acknowledge this option if this is not a reused connection
- already (which happens due to follow-location or during an HTTP
- authentication phase). CONNECT_ONLY transfers also refuse reuse. */
- if((data->set.reuse_fresh && !data->state.followlocation) ||
- data->set.connect_only)
- reuse = FALSE;
- else
- reuse = ConnectionExists(data, conn, &existing, &force_reuse, &waitpipe);
-
- if(reuse) {
- /*
- * We already have a connection for this, we got the former connection in
- * `existing` and thus we need to cleanup the one we just
- * allocated before we can move along and use `existing`.
- */
- VERBOSE(bool tls_upgraded = (!(conn->given->flags & PROTOPT_SSL) &&
+ if(data->conn) {
+ /* We attached an existing connection for this transfer. Copy
+ * over transfer specific properties over from needle. */
+ struct connectdata *conn = data->conn;
+ VERBOSE(bool tls_upgraded = (!(needle->given->flags & PROTOPT_SSL) &&
Curl_conn_is_ssl(conn, FIRSTSOCKET)));
- reuse_conn(data, conn, existing);
- conn = existing;
- *in_connect = conn;
+ conn->bits.reuse = TRUE;
+ url_conn_reuse_adjust(data, needle);
#ifndef CURL_DISABLE_PROXY
infof(data, "Reusing existing %s: connection%s with %s %s",
/* We have decided that we want a new connection. However, we may not
be able to do that if we have reached the limit of how many
connections we are allowed to open. */
- DEBUGF(infof(data, "new connection, bits.close=%d", conn->bits.close));
-
- if(conn->scheme->flags & PROTOPT_ALPN) {
- /* The protocol wants it, so set the bits if enabled in the easy handle
- (default) */
- if(data->set.ssl_enable_alpn)
- conn->bits.tls_enable_alpn = TRUE;
- }
+ DEBUGF(infof(data, "new connection, bits.close=%d", needle->bits.close));
if(waitpipe) {
/* There is a connection that *might* become usable for multiplexing
"soon", and we wait for that */
infof(data, "Waiting on connection to negotiate possible multiplexing.");
- connections_available = FALSE;
+ result = CURLE_NO_CONNECTION_AVAILABLE;
+ goto out;
}
else {
- switch(Curl_cpool_check_limits(data, conn)) {
+ switch(Curl_cpool_check_limits(data, needle)) {
case CPOOL_LIMIT_DEST:
infof(data, "No more connections allowed to host");
- connections_available = FALSE;
- break;
+ result = CURLE_NO_CONNECTION_AVAILABLE;
+ goto out;
case CPOOL_LIMIT_TOTAL:
if(data->master_mid != UINT32_MAX)
CURL_TRC_M(data, "Allowing sub-requests (like DoH) to override "
else {
infof(data, "No connections available, total of %zu reached.",
data->multi->max_total_connections);
- connections_available = FALSE;
+ result = CURLE_NO_CONNECTION_AVAILABLE;
+ goto out;
}
break;
default:
}
}
- if(!connections_available) {
- Curl_conn_free(data, conn);
- *in_connect = NULL;
-
- result = CURLE_NO_CONNECTION_AVAILABLE;
+ /* Convert needle into a full connection by filling in all the
+ * remaining parts like the cloned SSL configuration. */
+ result = Curl_ssl_conn_config_init(data, needle);
+ if(result) {
+ DEBUGF(curl_mfprintf(stderr, "Error: init connection ssl config\n"));
goto out;
}
- else {
- /*
- * This is a brand new connection, so let's store it in the connection
- * cache of ours!
- */
- result = Curl_ssl_conn_config_init(data, conn);
- if(result) {
- DEBUGF(curl_mfprintf(stderr, "Error: init connection ssl config\n"));
- goto out;
- }
+ /* attach it and no longer own it */
+ Curl_attach_connection(data, needle);
+ needle = NULL;
- Curl_attach_connection(data, conn);
- result = Curl_cpool_add(data, conn);
- if(result)
- goto out;
- }
+ result = Curl_cpool_add(data, data->conn);
+ if(result)
+ goto out;
#ifdef USE_NTLM
/* If NTLM is requested in a part of this connection, make sure we do not
}
/* Setup and init stuff before DO starts, in preparing for the transfer. */
- result = Curl_init_do(data, conn);
+ result = Curl_init_do(data, data->conn);
if(result)
goto out;
- /*
- * Setup whatever necessary for a resumed transfer
- */
+ /* Setup whatever necessary for a resumed transfer */
result = setup_range(data);
if(result)
goto out;
- /* Continue connectdata initialization here. */
-
- if(conn->bits.reuse) {
- /* We are reusing the connection - no need to resolve anything, and
- idnconvert_hostname() was called already in create_conn() for the reuse
- case. */
- *reusedp = TRUE;
- }
-
/* persist the scheme and handler the transfer is using */
- data->info.conn_scheme = conn->scheme->name;
+ data->info.conn_scheme = data->conn->scheme->name;
/* conn_protocol can only provide "old" protocols */
- data->info.conn_protocol = (conn->scheme->protocol) & CURLPROTO_MASK;
+ data->info.conn_protocol = (data->conn->scheme->protocol) & CURLPROTO_MASK;
data->info.used_proxy =
#ifdef CURL_DISABLE_PROXY
0
#else
- conn->bits.proxy
+ data->conn->bits.proxy
#endif
;
- /* Everything general done, inform filters that they need
- * to prepare for a data transfer. */
+ /* Lastly, inform connection filters that a new transfer is attached */
result = Curl_conn_ev_data_setup(data);
out:
+ if(needle)
+ Curl_conn_free(data, needle);
+ DEBUGASSERT(result || data->conn);
return result;
}
{
CURLcode result;
struct connectdata *conn;
- bool reused = FALSE;
*asyncp = FALSE; /* assume synchronous resolves by default */
*protocol_done = FALSE;
/* Set the request to virgin state based on transfer settings */
Curl_req_hard_reset(&data->req, data);
- /* call the stuff that needs to be called */
- result = create_conn(data, &conn, &reused);
+ /* Get or create a connection for the transfer. */
+ result = url_find_or_create_conn(data);
+ conn = data->conn;
if(result == CURLE_NO_CONNECTION_AVAILABLE) {
DEBUGASSERT(!conn);
if(!result) {
DEBUGASSERT(conn);
Curl_pgrsTime(data, TIMER_POSTQUEUE);
- if(reused) {
+ if(conn->bits.reuse) {
if(conn->attached_xfers > 1)
/* multiplexed */
*protocol_done = TRUE;