mirror of
https://github.com/curl/curl.git
synced 2026-04-11 12:01:42 +08:00
Make cf-https-connect work async correctly: - only start first baller when at least one A/AAAA address is available - select first connect attempt after that with HTTPS-RR info there or not. - select second connect attempt only when HTTPS-RR is resolved (may have resolved to "not known") and select possible ALPN from things known by then. May not select any second attempt when first already covers everything. This means when the HTTPS-RR is known at/before the first address is resolved, everything behaves as before. When the HTTPS-RR is late, a first connection attempt will have been started. Any ALPN preference from the HTTPS-RR that is not already ongoing will then start the second attempt. For HTTPS-RRs that recommend 2 or more ALPNs, the first will always be attempted: either it is already ongong or it will be the ALPN for the second attempt. The 2nd ALPN recommendation from HTTPS-RR *may* be honored or not, depending on what is already selected. The difference in behaviour between early/late HTTPS-RR resolve cannot be helped - unless we do not perform any attempts before it arrives. Trade offs. Closes #21267
824 lines
22 KiB
C
824 lines
22 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
*
|
|
* This software is licensed as described in the file COPYING, which
|
|
* you should have received as part of this distribution. The terms
|
|
* are also available at https://curl.se/docs/copyright.html.
|
|
*
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
* SPDX-License-Identifier: curl
|
|
*
|
|
***************************************************************************/
|
|
#include "curl_setup.h"
|
|
|
|
#ifndef CURL_DISABLE_HTTP
|
|
|
|
#include "urldata.h"
|
|
#include "curl_trc.h"
|
|
#include "cfilters.h"
|
|
#include "cf-dns.h"
|
|
#include "connect.h"
|
|
#include "hostip.h"
|
|
#include "httpsrr.h"
|
|
#include "multiif.h"
|
|
#include "cf-https-connect.h"
|
|
#include "http2.h"
|
|
#include "progress.h"
|
|
#include "select.h"
|
|
#include "vquic/vquic.h"
|
|
|
|
typedef enum {
|
|
CF_HC_RESOLV,
|
|
CF_HC_INIT,
|
|
CF_HC_CONNECT,
|
|
CF_HC_SUCCESS,
|
|
CF_HC_FAILURE
|
|
} cf_hc_state;
|
|
|
|
struct cf_hc_baller {
|
|
const char *name;
|
|
struct Curl_cfilter *cf;
|
|
CURLcode result;
|
|
struct curltime started;
|
|
int reply_ms;
|
|
uint8_t transport;
|
|
enum alpnid alpn_id;
|
|
BIT(shutdown);
|
|
};
|
|
|
|
static void cf_hc_baller_discard(struct cf_hc_baller *b,
|
|
struct Curl_easy *data)
|
|
{
|
|
if(b->cf) {
|
|
Curl_conn_cf_close(b->cf, data);
|
|
Curl_conn_cf_discard_chain(&b->cf, data);
|
|
b->cf = NULL;
|
|
}
|
|
}
|
|
|
|
static bool cf_hc_baller_is_connecting(struct cf_hc_baller *b)
|
|
{
|
|
return b->cf && !b->result;
|
|
}
|
|
|
|
static bool cf_hc_baller_has_started(struct cf_hc_baller *b)
|
|
{
|
|
return !!b->cf;
|
|
}
|
|
|
|
static int cf_hc_baller_reply_ms(struct cf_hc_baller *b,
|
|
struct Curl_easy *data)
|
|
{
|
|
if(b->cf && (b->reply_ms < 0))
|
|
b->cf->cft->query(b->cf, data, CF_QUERY_CONNECT_REPLY_MS,
|
|
&b->reply_ms, NULL);
|
|
return b->reply_ms;
|
|
}
|
|
|
|
static bool cf_hc_baller_data_pending(struct cf_hc_baller *b,
|
|
const struct Curl_easy *data)
|
|
{
|
|
return b->cf && !b->result && b->cf->cft->has_data_pending(b->cf, data);
|
|
}
|
|
|
|
static bool cf_hc_baller_needs_flush(struct cf_hc_baller *b,
|
|
struct Curl_easy *data)
|
|
{
|
|
return b->cf && !b->result && Curl_conn_cf_needs_flush(b->cf, data);
|
|
}
|
|
|
|
static CURLcode cf_hc_baller_cntrl(struct cf_hc_baller *b,
|
|
struct Curl_easy *data,
|
|
int event, int arg1, void *arg2)
|
|
{
|
|
if(b->cf && !b->result)
|
|
return Curl_conn_cf_cntrl(b->cf, data, FALSE, event, arg1, arg2);
|
|
return CURLE_OK;
|
|
}
|
|
|
|
struct cf_hc_ctx {
|
|
cf_hc_state state;
|
|
struct curltime started; /* when connect started */
|
|
CURLcode result; /* overall result */
|
|
CURLcode check_h3_result;
|
|
struct cf_hc_baller ballers[2];
|
|
size_t baller_count;
|
|
timediff_t soft_eyeballs_timeout_ms;
|
|
timediff_t hard_eyeballs_timeout_ms;
|
|
uint8_t def_transport;
|
|
BIT(httpsrr_resolved);
|
|
BIT(checked_h3);
|
|
BIT(ballers_complete);
|
|
};
|
|
|
|
static void cf_hc_ctx_close(struct Curl_easy *data,
|
|
struct cf_hc_ctx *ctx)
|
|
{
|
|
if(ctx) {
|
|
size_t i;
|
|
for(i = 0; i < ctx->baller_count; ++i)
|
|
cf_hc_baller_discard(&ctx->ballers[i], data);
|
|
}
|
|
}
|
|
|
|
static void cf_hc_ctx_destroy(struct Curl_easy *data,
|
|
struct cf_hc_ctx *ctx)
|
|
{
|
|
if(ctx) {
|
|
cf_hc_ctx_close(data, ctx);
|
|
curlx_free(ctx);
|
|
}
|
|
}
|
|
|
|
static void cf_hc_baller_assign(struct cf_hc_baller *b,
|
|
enum alpnid alpn_id,
|
|
uint8_t def_transport)
|
|
{
|
|
b->alpn_id = alpn_id;
|
|
b->transport = def_transport;
|
|
b->cf = NULL;
|
|
b->result = CURLE_OK;
|
|
b->reply_ms = -1;
|
|
b->shutdown = FALSE;
|
|
switch(b->alpn_id) {
|
|
case ALPN_h3:
|
|
b->name = "h3";
|
|
b->transport = TRNSPRT_QUIC;
|
|
break;
|
|
case ALPN_h2:
|
|
b->name = "h2";
|
|
break;
|
|
case ALPN_h1:
|
|
b->name = "h1";
|
|
break;
|
|
case ALPN_none:
|
|
b->name = "no-alpn";
|
|
break;
|
|
default:
|
|
b->result = CURLE_FAILED_INIT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void cf_hc_baller_init(struct cf_hc_baller *b,
|
|
struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct Curl_cfilter *save = cf->next;
|
|
|
|
cf->next = NULL;
|
|
b->started = *Curl_pgrs_now(data);
|
|
b->result = Curl_cf_setup_insert_after(cf, data, b->transport,
|
|
CURL_CF_SSL_ENABLE);
|
|
b->cf = cf->next;
|
|
cf->next = save;
|
|
}
|
|
|
|
static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b,
|
|
struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
bool *done)
|
|
{
|
|
struct Curl_cfilter *save = cf->next;
|
|
|
|
cf->next = b->cf;
|
|
b->result = Curl_conn_cf_connect(cf->next, data, done);
|
|
b->cf = cf->next; /* it might mutate */
|
|
cf->next = save;
|
|
return b->result;
|
|
}
|
|
|
|
static CURLcode baller_connected(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct cf_hc_baller *winner)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
|
|
/* Make the winner's connection filter out own sub-filter, check, move,
|
|
* close all remaining. */
|
|
if(cf->next) {
|
|
DEBUGASSERT(0);
|
|
return CURLE_FAILED_INIT;
|
|
}
|
|
if(!winner->cf) {
|
|
DEBUGASSERT(0);
|
|
return CURLE_FAILED_INIT;
|
|
}
|
|
|
|
cf->next = winner->cf;
|
|
winner->cf = NULL;
|
|
ctx->state = CF_HC_SUCCESS;
|
|
cf->connected = TRUE;
|
|
|
|
cf_hc_ctx_close(data, ctx);
|
|
/* ballers may have failf()'d, the winner resets it, so our
|
|
* errorbuf is clean again. */
|
|
Curl_reset_fail(data);
|
|
|
|
#ifdef USE_NGHTTP2
|
|
{
|
|
/* For a negotiated HTTP/2 connection insert the h2 filter. */
|
|
const char *alpn = Curl_conn_cf_get_alpn_negotiated(cf->next, data);
|
|
if(alpn && !strcmp("h2", alpn)) {
|
|
CURLcode result = Curl_http2_switch_at(cf, data);
|
|
if(result) {
|
|
ctx->state = CF_HC_FAILURE;
|
|
ctx->result = result;
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static bool time_to_start_baller2(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
timediff_t elapsed_ms;
|
|
|
|
if(ctx->baller_count < 2)
|
|
return FALSE;
|
|
else if(cf_hc_baller_has_started(&ctx->ballers[1]))
|
|
return FALSE;
|
|
else if(ctx->ballers[0].result) {
|
|
CURL_TRC_CF(data, cf, "%s baller failed, starting %s",
|
|
ctx->ballers[0].name, ctx->ballers[1].name);
|
|
return TRUE;
|
|
}
|
|
|
|
elapsed_ms = curlx_ptimediff_ms(Curl_pgrs_now(data), &ctx->started);
|
|
if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) {
|
|
CURL_TRC_CF(data, cf, "%s inconclusive after %" FMT_TIMEDIFF_T ", "
|
|
"starting %s", ctx->ballers[0].name,
|
|
ctx->hard_eyeballs_timeout_ms, ctx->ballers[1].name);
|
|
return TRUE;
|
|
}
|
|
else if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) {
|
|
if(cf_hc_baller_reply_ms(&ctx->ballers[0], data) < 0) {
|
|
CURL_TRC_CF(data, cf, "%s has not seen any data after %"
|
|
FMT_TIMEDIFF_T "ms, starting %s",
|
|
ctx->ballers[0].name, ctx->soft_eyeballs_timeout_ms,
|
|
ctx->ballers[1].name);
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static bool cf_hc_may_h3(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
if(!ctx->checked_h3) {
|
|
ctx->check_h3_result =
|
|
Curl_conn_may_http3(data, cf->conn, ctx->def_transport);
|
|
ctx->checked_h3 = TRUE;
|
|
}
|
|
return !ctx->check_h3_result;
|
|
}
|
|
|
|
static enum alpnid cf_hc_get_httpsrr_alpn(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
enum alpnid not_this_one)
|
|
{
|
|
#ifdef USE_HTTPSRR
|
|
/* Is there an HTTPSRR use its ALPNs here.
|
|
* We are here after having selected a connection to a host+port and
|
|
* can no longer change that. Any HTTPSRR advice for other hosts and ports
|
|
* we need to ignore. */
|
|
const struct Curl_https_rrinfo *rr;
|
|
size_t i;
|
|
|
|
/* Do we have HTTPS-RR information? */
|
|
rr = Curl_conn_dns_get_https(data, cf->sockindex);
|
|
|
|
if(rr && !rr->no_def_alpn && /* ALPNs are defaults */
|
|
(!rr->target || /* for same host */
|
|
!rr->target[0] ||
|
|
(rr->target[0] == '.' &&
|
|
!rr->target[1])) &&
|
|
(!rr->port_set || /* for same port */
|
|
rr->port == cf->conn->remote_port)) {
|
|
for(i = 0; i < CURL_ARRAYSIZE(rr->alpns); ++i) {
|
|
enum alpnid alpn_rr = (enum alpnid)rr->alpns[i];
|
|
if(alpn_rr == not_this_one) /* don't want this one */
|
|
continue;
|
|
switch(alpn_rr) {
|
|
case ALPN_h3:
|
|
if((data->state.http_neg.allowed & CURL_HTTP_V3x) &&
|
|
cf_hc_may_h3(cf, data)) {
|
|
return alpn_rr;
|
|
}
|
|
break;
|
|
case ALPN_h2:
|
|
if(data->state.http_neg.allowed & CURL_HTTP_V2x) {
|
|
return alpn_rr;
|
|
}
|
|
break;
|
|
case ALPN_h1:
|
|
if(data->state.http_neg.allowed & CURL_HTTP_V1x) {
|
|
return alpn_rr;
|
|
}
|
|
break;
|
|
default: /* ignore */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
(void)cf;
|
|
(void)data;
|
|
(void)not_this_one;
|
|
#endif
|
|
return ALPN_none;
|
|
}
|
|
|
|
static enum alpnid cf_hc_get_pref_alpn(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
enum alpnid not_this_one)
|
|
{
|
|
if((data->state.http_neg.preferred & data->state.http_neg.allowed)) {
|
|
switch(data->state.http_neg.preferred) {
|
|
case CURL_HTTP_V3x:
|
|
if(cf_hc_may_h3(cf, data) && (ALPN_h3 != not_this_one))
|
|
return ALPN_h3;
|
|
break;
|
|
case CURL_HTTP_V2x:
|
|
if(ALPN_h2 != not_this_one)
|
|
return ALPN_h2;
|
|
break;
|
|
case CURL_HTTP_V1x:
|
|
/* If we are trying h2 already, h1 is already used as fallback */
|
|
if((ALPN_h1 != not_this_one) && (ALPN_h2 != not_this_one))
|
|
return ALPN_h1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return ALPN_none;
|
|
}
|
|
|
|
static enum alpnid cf_hc_get_first_alpn(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
http_majors choices,
|
|
enum alpnid not_this_one)
|
|
{
|
|
if((ALPN_h3 != not_this_one) && (choices & CURL_HTTP_V3x) &&
|
|
cf_hc_may_h3(cf, data)) {
|
|
return ALPN_h3;
|
|
}
|
|
if((ALPN_h2 != not_this_one) && (choices & CURL_HTTP_V2x)) {
|
|
return ALPN_h2;
|
|
}
|
|
/* If we are trying h2 already, h1 is already used as fallback */
|
|
if((ALPN_h1 != not_this_one) && (ALPN_h2 != not_this_one) &&
|
|
(choices & CURL_HTTP_V1x)) {
|
|
return ALPN_h1;
|
|
}
|
|
return ALPN_none;
|
|
}
|
|
|
|
static CURLcode cf_hc_set_baller1(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
enum alpnid alpn1 = ALPN_none;
|
|
VERBOSE(const char *source = "HTTPS-RR");
|
|
|
|
DEBUGASSERT(cf->conn->bits.tls_enable_alpn);
|
|
|
|
alpn1 = cf_hc_get_httpsrr_alpn(cf, data, ALPN_none);
|
|
if(alpn1 == ALPN_none) {
|
|
/* preference is configured and allowed, can we use it? */
|
|
VERBOSE(source = "preferred version");
|
|
alpn1 = cf_hc_get_pref_alpn(cf, data, ALPN_none);
|
|
}
|
|
if(alpn1 == ALPN_none) {
|
|
VERBOSE(source = "wanted versions");
|
|
alpn1 = cf_hc_get_first_alpn(cf, data,
|
|
data->state.http_neg.wanted,
|
|
ALPN_none);
|
|
}
|
|
if(alpn1 == ALPN_none) {
|
|
VERBOSE(source = "allowed versions");
|
|
alpn1 = cf_hc_get_first_alpn(cf, data,
|
|
data->state.http_neg.allowed,
|
|
ALPN_none);
|
|
}
|
|
|
|
if(alpn1 == ALPN_none) {
|
|
/* None of the wanted/allowed HTTP versions could be chosen */
|
|
if(ctx->check_h3_result) {
|
|
CURL_TRC_CF(data, cf, "unable to use HTTP/3");
|
|
return ctx->check_h3_result;
|
|
}
|
|
CURL_TRC_CF(data, cf, "unable to select HTTP version");
|
|
return CURLE_FAILED_INIT;
|
|
}
|
|
|
|
cf_hc_baller_assign(&ctx->ballers[0], alpn1, ctx->def_transport);
|
|
ctx->baller_count = 1;
|
|
CURL_TRC_CF(data, cf, "1st attempt uses %s from %s",
|
|
ctx->ballers[0].name, source);
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static void cf_hc_set_baller2(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
enum alpnid alpn2 = ALPN_none, alpn1 = ctx->ballers[0].alpn_id;
|
|
VERBOSE(const char *source = "HTTPS-RR");
|
|
|
|
if(ctx->ballers_complete)
|
|
return; /* already done */
|
|
if(!ctx->httpsrr_resolved)
|
|
return; /* HTTPS-RR pending */
|
|
|
|
alpn2 = cf_hc_get_httpsrr_alpn(cf, data, alpn1);
|
|
if(alpn2 == ALPN_none) {
|
|
/* preference is configured and allowed, can we use it? */
|
|
VERBOSE(source = "preferred version");
|
|
alpn2 = cf_hc_get_pref_alpn(cf, data, alpn1);
|
|
}
|
|
if(alpn2 == ALPN_none) {
|
|
VERBOSE(source = "wanted versions");
|
|
alpn2 = cf_hc_get_first_alpn(cf, data,
|
|
data->state.http_neg.wanted,
|
|
alpn1);
|
|
}
|
|
|
|
if(alpn2 != ALPN_none) {
|
|
cf_hc_baller_assign(&ctx->ballers[1], alpn2, ctx->def_transport);
|
|
ctx->baller_count = 2;
|
|
CURL_TRC_CF(data, cf, "2nd attempt uses %s from %s",
|
|
ctx->ballers[1].name, source);
|
|
}
|
|
ctx->ballers_complete = TRUE;
|
|
}
|
|
|
|
static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
bool *done)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
if(cf->connected) {
|
|
*done = TRUE;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
*done = FALSE;
|
|
|
|
if(!ctx->httpsrr_resolved)
|
|
ctx->httpsrr_resolved = Curl_conn_dns_resolved_https(data, cf->sockindex);
|
|
|
|
switch(ctx->state) {
|
|
case CF_HC_RESOLV:
|
|
/* Without any addressinfo, delay the start of balling. */
|
|
if(!Curl_conn_dns_has_any_ai(data, cf->sockindex))
|
|
return CURLE_OK;
|
|
ctx->state = CF_HC_INIT;
|
|
FALLTHROUGH();
|
|
|
|
case CF_HC_INIT:
|
|
DEBUGASSERT(!cf->next);
|
|
CURL_TRC_CF(data, cf, "connect, init");
|
|
result = cf_hc_set_baller1(cf, data);
|
|
if(result) {
|
|
ctx->result = result;
|
|
ctx->state = CF_HC_FAILURE;
|
|
goto out;
|
|
}
|
|
cf_hc_set_baller2(cf, data);
|
|
ctx->started = *Curl_pgrs_now(data);
|
|
cf_hc_baller_init(&ctx->ballers[0], cf, data);
|
|
if((ctx->baller_count > 1) || !ctx->ballers_complete) {
|
|
Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS);
|
|
}
|
|
ctx->state = CF_HC_CONNECT;
|
|
FALLTHROUGH();
|
|
|
|
case CF_HC_CONNECT:
|
|
if(!ctx->ballers_complete)
|
|
cf_hc_set_baller2(cf, data);
|
|
|
|
if(cf_hc_baller_is_connecting(&ctx->ballers[0])) {
|
|
result = cf_hc_baller_connect(&ctx->ballers[0], cf, data, done);
|
|
if(!result && *done) {
|
|
result = baller_connected(cf, data, &ctx->ballers[0]);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if(time_to_start_baller2(cf, data)) {
|
|
cf_hc_baller_init(&ctx->ballers[1], cf, data);
|
|
}
|
|
|
|
if(cf_hc_baller_is_connecting(&ctx->ballers[1])) {
|
|
result = cf_hc_baller_connect(&ctx->ballers[1], cf, data, done);
|
|
if(!result && *done) {
|
|
result = baller_connected(cf, data, &ctx->ballers[1]);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if(ctx->ballers[0].result &&
|
|
(ctx->ballers[1].result ||
|
|
(ctx->ballers_complete && (ctx->baller_count < 2)))) {
|
|
/* all have failed. we give up */
|
|
CURL_TRC_CF(data, cf, "connect, all attempts failed");
|
|
ctx->result = result = ctx->ballers[0].result;
|
|
ctx->state = CF_HC_FAILURE;
|
|
goto out;
|
|
}
|
|
result = CURLE_OK;
|
|
*done = FALSE;
|
|
break;
|
|
|
|
case CF_HC_FAILURE:
|
|
result = ctx->result;
|
|
cf->connected = FALSE;
|
|
*done = FALSE;
|
|
break;
|
|
|
|
case CF_HC_SUCCESS:
|
|
result = CURLE_OK;
|
|
cf->connected = TRUE;
|
|
*done = TRUE;
|
|
break;
|
|
}
|
|
|
|
out:
|
|
CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done);
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data, bool *done)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
size_t i;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
DEBUGASSERT(data);
|
|
if(cf->connected) {
|
|
*done = TRUE;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
/* shutdown all ballers that have not done so already. If one fails,
|
|
* continue shutting down others until all are shutdown. */
|
|
for(i = 0; i < ctx->baller_count; i++) {
|
|
struct cf_hc_baller *b = &ctx->ballers[i];
|
|
bool bdone = FALSE;
|
|
if(!cf_hc_baller_is_connecting(b) || b->shutdown)
|
|
continue;
|
|
b->result = b->cf->cft->do_shutdown(b->cf, data, &bdone);
|
|
if(b->result || bdone)
|
|
b->shutdown = TRUE; /* treat a failed shutdown as done */
|
|
}
|
|
|
|
*done = TRUE;
|
|
for(i = 0; i < ctx->baller_count; i++) {
|
|
if(!ctx->ballers[i].shutdown)
|
|
*done = FALSE;
|
|
}
|
|
if(*done) {
|
|
for(i = 0; i < ctx->baller_count; i++) {
|
|
if(ctx->ballers[i].result)
|
|
result = ctx->ballers[i].result;
|
|
}
|
|
}
|
|
CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done);
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_hc_adjust_pollset(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
struct easy_pollset *ps)
|
|
{
|
|
CURLcode result = CURLE_OK;
|
|
if(!cf->connected) {
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
size_t i;
|
|
|
|
for(i = 0; (i < ctx->baller_count) && !result; i++) {
|
|
struct cf_hc_baller *b = &ctx->ballers[i];
|
|
if(!cf_hc_baller_is_connecting(b))
|
|
continue;
|
|
result = Curl_conn_cf_adjust_pollset(b->cf, data, ps);
|
|
}
|
|
CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %d socks", result, ps->n);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static bool cf_hc_data_pending(struct Curl_cfilter *cf,
|
|
const struct Curl_easy *data)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
size_t i;
|
|
|
|
if(cf->connected)
|
|
return cf->next->cft->has_data_pending(cf->next, data);
|
|
|
|
for(i = 0; i < ctx->baller_count; i++)
|
|
if(cf_hc_baller_data_pending(&ctx->ballers[i], data))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static struct curltime cf_get_max_baller_time(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
int query)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
struct curltime t, tmax;
|
|
size_t i;
|
|
|
|
memset(&tmax, 0, sizeof(tmax));
|
|
for(i = 0; i < ctx->baller_count; i++) {
|
|
struct Curl_cfilter *cfb = ctx->ballers[i].cf;
|
|
memset(&t, 0, sizeof(t));
|
|
if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {
|
|
if((t.tv_sec || t.tv_usec) && curlx_ptimediff_us(&t, &tmax) > 0)
|
|
tmax = t;
|
|
}
|
|
}
|
|
return tmax;
|
|
}
|
|
|
|
static CURLcode cf_hc_query(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
int query, int *pres1, void *pres2)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
size_t i;
|
|
|
|
if(!cf->connected) {
|
|
switch(query) {
|
|
case CF_QUERY_TIMER_CONNECT: {
|
|
struct curltime *when = pres2;
|
|
*when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT);
|
|
return CURLE_OK;
|
|
}
|
|
case CF_QUERY_TIMER_APPCONNECT: {
|
|
struct curltime *when = pres2;
|
|
*when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT);
|
|
return CURLE_OK;
|
|
}
|
|
case CF_QUERY_NEED_FLUSH: {
|
|
for(i = 0; i < ctx->baller_count; i++)
|
|
if(cf_hc_baller_needs_flush(&ctx->ballers[i], data)) {
|
|
*pres1 = TRUE;
|
|
return CURLE_OK;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return cf->next ?
|
|
cf->next->cft->query(cf->next, data, query, pres1, pres2) :
|
|
CURLE_UNKNOWN_OPTION;
|
|
}
|
|
|
|
static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf,
|
|
struct Curl_easy *data,
|
|
int event, int arg1, void *arg2)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
CURLcode result = CURLE_OK;
|
|
size_t i;
|
|
|
|
if(!cf->connected) {
|
|
for(i = 0; i < ctx->baller_count; i++) {
|
|
result = cf_hc_baller_cntrl(&ctx->ballers[i], data, event, arg1, arg2);
|
|
if(result && (result != CURLE_AGAIN))
|
|
goto out;
|
|
}
|
|
result = CURLE_OK;
|
|
}
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data)
|
|
{
|
|
CURL_TRC_CF(data, cf, "close");
|
|
cf_hc_ctx_close(data, cf->ctx);
|
|
cf->connected = FALSE;
|
|
|
|
if(cf->next) {
|
|
cf->next->cft->do_close(cf->next, data);
|
|
Curl_conn_cf_discard_chain(&cf->next, data);
|
|
}
|
|
}
|
|
|
|
static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
|
|
{
|
|
struct cf_hc_ctx *ctx = cf->ctx;
|
|
|
|
CURL_TRC_CF(data, cf, "destroy");
|
|
cf_hc_ctx_destroy(data, ctx);
|
|
}
|
|
|
|
struct Curl_cftype Curl_cft_http_connect = {
|
|
"HTTPS-CONNECT",
|
|
0,
|
|
CURL_LOG_LVL_NONE,
|
|
cf_hc_destroy,
|
|
cf_hc_connect,
|
|
cf_hc_close,
|
|
cf_hc_shutdown,
|
|
cf_hc_adjust_pollset,
|
|
cf_hc_data_pending,
|
|
Curl_cf_def_send,
|
|
Curl_cf_def_recv,
|
|
cf_hc_cntrl,
|
|
Curl_cf_def_conn_is_alive,
|
|
Curl_cf_def_conn_keep_alive,
|
|
cf_hc_query,
|
|
};
|
|
|
|
static CURLcode cf_hc_create(struct Curl_cfilter **pcf,
|
|
struct Curl_easy *data,
|
|
uint8_t def_transport)
|
|
{
|
|
struct Curl_cfilter *cf = NULL;
|
|
struct cf_hc_ctx *ctx;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
ctx = curlx_calloc(1, sizeof(*ctx));
|
|
if(!ctx) {
|
|
result = CURLE_OUT_OF_MEMORY;
|
|
goto out;
|
|
}
|
|
ctx->def_transport = def_transport;
|
|
|
|
result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx);
|
|
if(result)
|
|
goto out;
|
|
ctx = NULL;
|
|
|
|
out:
|
|
*pcf = result ? NULL : cf;
|
|
cf_hc_ctx_destroy(data, ctx);
|
|
return result;
|
|
}
|
|
|
|
static CURLcode cf_hc_add(struct Curl_easy *data,
|
|
struct connectdata *conn,
|
|
int sockindex,
|
|
uint8_t def_transport)
|
|
{
|
|
struct Curl_cfilter *cf;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
DEBUGASSERT(data);
|
|
result = cf_hc_create(&cf, data, def_transport);
|
|
if(result)
|
|
goto out;
|
|
Curl_conn_cf_add(data, conn, sockindex, cf);
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
CURLcode Curl_cf_https_setup(struct Curl_easy *data,
|
|
struct connectdata *conn,
|
|
int sockindex)
|
|
{
|
|
CURLcode result = CURLE_OK;
|
|
|
|
DEBUGASSERT(conn->scheme->protocol == CURLPROTO_HTTPS);
|
|
|
|
if((conn->scheme->protocol != CURLPROTO_HTTPS) ||
|
|
!conn->bits.tls_enable_alpn)
|
|
goto out;
|
|
|
|
result = cf_hc_add(data, conn, sockindex, conn->transport_wanted);
|
|
|
|
out:
|
|
return result;
|
|
}
|
|
|
|
#endif /* !CURL_DISABLE_HTTP */
|