123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551 |
- /***************************************************************************
- * _ _ ____ _
- * 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"
- #if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER)
- #include "urldata.h"
- #include <curl/curl.h>
- #include "curl_trc.h"
- #include "cfilters.h"
- #include "connect.h"
- #include "multiif.h"
- #include "cf-https-connect.h"
- #include "http2.h"
- #include "vquic/vquic.h"
- /* The last 3 #include files should be in this order */
- #include "curl_printf.h"
- #include "curl_memory.h"
- #include "memdebug.h"
- typedef enum {
- 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;
- bool enabled;
- };
- static void cf_hc_baller_reset(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;
- }
- b->result = CURLE_OK;
- b->reply_ms = -1;
- }
- static bool cf_hc_baller_is_active(struct cf_hc_baller *b)
- {
- return b->enabled && 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->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);
- }
- struct cf_hc_ctx {
- cf_hc_state state;
- const struct Curl_dns_entry *remotehost;
- struct curltime started; /* when connect started */
- CURLcode result; /* overall result */
- struct cf_hc_baller h3_baller;
- struct cf_hc_baller h21_baller;
- int soft_eyeballs_timeout_ms;
- int hard_eyeballs_timeout_ms;
- };
- static void cf_hc_baller_init(struct cf_hc_baller *b,
- struct Curl_cfilter *cf,
- struct Curl_easy *data,
- const char *name,
- int transport)
- {
- struct cf_hc_ctx *ctx = cf->ctx;
- struct Curl_cfilter *save = cf->next;
- b->name = name;
- cf->next = NULL;
- b->started = Curl_now();
- b->result = Curl_cf_setup_insert_after(cf, data, ctx->remotehost,
- 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, FALSE, done);
- b->cf = cf->next; /* it might mutate */
- cf->next = save;
- return b->result;
- }
- static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data)
- {
- struct cf_hc_ctx *ctx = cf->ctx;
- if(ctx) {
- cf_hc_baller_reset(&ctx->h3_baller, data);
- cf_hc_baller_reset(&ctx->h21_baller, data);
- ctx->state = CF_HC_INIT;
- ctx->result = CURLE_OK;
- ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout;
- ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2;
- }
- }
- static CURLcode baller_connected(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- struct cf_hc_baller *winner)
- {
- struct cf_hc_ctx *ctx = cf->ctx;
- CURLcode result = CURLE_OK;
- DEBUGASSERT(winner->cf);
- if(winner != &ctx->h3_baller)
- cf_hc_baller_reset(&ctx->h3_baller, data);
- if(winner != &ctx->h21_baller)
- cf_hc_baller_reset(&ctx->h21_baller, data);
- CURL_TRC_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms",
- winner->name, (int)Curl_timediff(Curl_now(), winner->started),
- cf_hc_baller_reply_ms(winner, data));
- cf->next = winner->cf;
- winner->cf = NULL;
- switch(cf->conn->alpn) {
- case CURL_HTTP_VERSION_3:
- infof(data, "using HTTP/3");
- break;
- case CURL_HTTP_VERSION_2:
- #ifdef USE_NGHTTP2
- /* Using nghttp2, we add the filter "below" us, so when the conn
- * closes, we tear it down for a fresh reconnect */
- result = Curl_http2_switch_at(cf, data);
- if(result) {
- ctx->state = CF_HC_FAILURE;
- ctx->result = result;
- return result;
- }
- #endif
- infof(data, "using HTTP/2");
- break;
- case CURL_HTTP_VERSION_1_1:
- infof(data, "using HTTP/1.1");
- break;
- default:
- infof(data, "using HTTP/1.x");
- break;
- }
- ctx->state = CF_HC_SUCCESS;
- cf->connected = TRUE;
- Curl_conn_cf_cntrl(cf->next, data, TRUE,
- CF_CTRL_CONN_INFO_UPDATE, 0, NULL);
- return result;
- }
- static bool time_to_start_h21(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- struct curltime now)
- {
- struct cf_hc_ctx *ctx = cf->ctx;
- timediff_t elapsed_ms;
- if(!ctx->h21_baller.enabled || cf_hc_baller_has_started(&ctx->h21_baller))
- return FALSE;
- if(!ctx->h3_baller.enabled || !cf_hc_baller_is_active(&ctx->h3_baller))
- return TRUE;
- elapsed_ms = Curl_timediff(now, ctx->started);
- if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) {
- CURL_TRC_CF(data, cf, "hard timeout of %dms reached, starting h21",
- ctx->hard_eyeballs_timeout_ms);
- return TRUE;
- }
- if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) {
- if(cf_hc_baller_reply_ms(&ctx->h3_baller, data) < 0) {
- CURL_TRC_CF(data, cf, "soft timeout of %dms reached, h3 has not "
- "seen any data, starting h21",
- ctx->soft_eyeballs_timeout_ms);
- return TRUE;
- }
- /* set the effective hard timeout again */
- Curl_expire(data, ctx->hard_eyeballs_timeout_ms - elapsed_ms,
- EXPIRE_ALPN_EYEBALLS);
- }
- return FALSE;
- }
- static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- bool blocking, bool *done)
- {
- struct cf_hc_ctx *ctx = cf->ctx;
- struct curltime now;
- CURLcode result = CURLE_OK;
- (void)blocking;
- if(cf->connected) {
- *done = TRUE;
- return CURLE_OK;
- }
- *done = FALSE;
- now = Curl_now();
- switch(ctx->state) {
- case CF_HC_INIT:
- DEBUGASSERT(!ctx->h3_baller.cf);
- DEBUGASSERT(!ctx->h21_baller.cf);
- DEBUGASSERT(!cf->next);
- CURL_TRC_CF(data, cf, "connect, init");
- ctx->started = now;
- if(ctx->h3_baller.enabled) {
- cf_hc_baller_init(&ctx->h3_baller, cf, data, "h3", TRNSPRT_QUIC);
- if(ctx->h21_baller.enabled)
- Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS);
- }
- else if(ctx->h21_baller.enabled)
- cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21",
- cf->conn->transport);
- ctx->state = CF_HC_CONNECT;
- /* FALLTHROUGH */
- case CF_HC_CONNECT:
- if(cf_hc_baller_is_active(&ctx->h3_baller)) {
- result = cf_hc_baller_connect(&ctx->h3_baller, cf, data, done);
- if(!result && *done) {
- result = baller_connected(cf, data, &ctx->h3_baller);
- goto out;
- }
- }
- if(time_to_start_h21(cf, data, now)) {
- cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21",
- cf->conn->transport);
- }
- if(cf_hc_baller_is_active(&ctx->h21_baller)) {
- CURL_TRC_CF(data, cf, "connect, check h21");
- result = cf_hc_baller_connect(&ctx->h21_baller, cf, data, done);
- if(!result && *done) {
- result = baller_connected(cf, data, &ctx->h21_baller);
- goto out;
- }
- }
- if((!ctx->h3_baller.enabled || ctx->h3_baller.result) &&
- (!ctx->h21_baller.enabled || ctx->h21_baller.result)) {
- /* both failed or disabled. we give up */
- CURL_TRC_CF(data, cf, "connect, all failed");
- result = ctx->result = ctx->h3_baller.enabled?
- ctx->h3_baller.result : ctx->h21_baller.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 int cf_hc_get_select_socks(struct Curl_cfilter *cf,
- struct Curl_easy *data,
- curl_socket_t *socks)
- {
- struct cf_hc_ctx *ctx = cf->ctx;
- size_t i, j, s;
- int brc, rc = GETSOCK_BLANK;
- curl_socket_t bsocks[MAX_SOCKSPEREASYHANDLE];
- struct cf_hc_baller *ballers[2];
- if(cf->connected)
- return cf->next->cft->get_select_socks(cf->next, data, socks);
- ballers[0] = &ctx->h3_baller;
- ballers[1] = &ctx->h21_baller;
- for(i = s = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
- struct cf_hc_baller *b = ballers[i];
- if(!cf_hc_baller_is_active(b))
- continue;
- brc = Curl_conn_cf_get_select_socks(b->cf, data, bsocks);
- CURL_TRC_CF(data, cf, "get_selected_socks(%s) -> %x", b->name, brc);
- if(!brc)
- continue;
- for(j = 0; j < MAX_SOCKSPEREASYHANDLE && s < MAX_SOCKSPEREASYHANDLE; ++j) {
- if((brc & GETSOCK_WRITESOCK(j)) || (brc & GETSOCK_READSOCK(j))) {
- socks[s] = bsocks[j];
- if(brc & GETSOCK_WRITESOCK(j))
- rc |= GETSOCK_WRITESOCK(s);
- if(brc & GETSOCK_READSOCK(j))
- rc |= GETSOCK_READSOCK(s);
- s++;
- }
- }
- }
- CURL_TRC_CF(data, cf, "get_selected_socks -> %x", rc);
- return rc;
- }
- static bool cf_hc_data_pending(struct Curl_cfilter *cf,
- const struct Curl_easy *data)
- {
- struct cf_hc_ctx *ctx = cf->ctx;
- if(cf->connected)
- return cf->next->cft->has_data_pending(cf->next, data);
- CURL_TRC_CF((struct Curl_easy *)data, cf, "data_pending");
- return cf_hc_baller_data_pending(&ctx->h3_baller, data)
- || cf_hc_baller_data_pending(&ctx->h21_baller, data);
- }
- 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 Curl_cfilter *cfb;
- struct curltime t, tmax;
- memset(&tmax, 0, sizeof(tmax));
- memset(&t, 0, sizeof(t));
- cfb = ctx->h21_baller.enabled? ctx->h21_baller.cf : NULL;
- if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {
- if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0)
- tmax = t;
- }
- memset(&t, 0, sizeof(t));
- cfb = ctx->h3_baller.enabled? ctx->h3_baller.cf : NULL;
- if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {
- if((t.tv_sec || t.tv_usec) && Curl_timediff_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)
- {
- 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;
- }
- default:
- break;
- }
- }
- return cf->next?
- cf->next->cft->query(cf->next, data, query, pres1, pres2) :
- CURLE_UNKNOWN_OPTION;
- }
- static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data)
- {
- CURL_TRC_CF(data, cf, "close");
- cf_hc_reset(cf, data);
- 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;
- (void)data;
- CURL_TRC_CF(data, cf, "destroy");
- cf_hc_reset(cf, data);
- Curl_safefree(ctx);
- }
- struct Curl_cftype Curl_cft_http_connect = {
- "HTTPS-CONNECT",
- 0,
- CURL_LOG_LVL_NONE,
- cf_hc_destroy,
- cf_hc_connect,
- cf_hc_close,
- Curl_cf_def_get_host,
- cf_hc_get_select_socks,
- cf_hc_data_pending,
- Curl_cf_def_send,
- Curl_cf_def_recv,
- Curl_cf_def_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,
- const struct Curl_dns_entry *remotehost,
- bool try_h3, bool try_h21)
- {
- struct Curl_cfilter *cf = NULL;
- struct cf_hc_ctx *ctx;
- CURLcode result = CURLE_OK;
- (void)data;
- ctx = calloc(sizeof(*ctx), 1);
- if(!ctx) {
- result = CURLE_OUT_OF_MEMORY;
- goto out;
- }
- ctx->remotehost = remotehost;
- ctx->h3_baller.enabled = try_h3;
- ctx->h21_baller.enabled = try_h21;
- result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx);
- if(result)
- goto out;
- ctx = NULL;
- cf_hc_reset(cf, data);
- out:
- *pcf = result? NULL : cf;
- free(ctx);
- return result;
- }
- static CURLcode cf_http_connect_add(struct Curl_easy *data,
- struct connectdata *conn,
- int sockindex,
- const struct Curl_dns_entry *remotehost,
- bool try_h3, bool try_h21)
- {
- struct Curl_cfilter *cf;
- CURLcode result = CURLE_OK;
- DEBUGASSERT(data);
- result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21);
- 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,
- const struct Curl_dns_entry *remotehost)
- {
- bool try_h3 = FALSE, try_h21 = TRUE; /* defaults, for now */
- CURLcode result = CURLE_OK;
- (void)sockindex;
- (void)remotehost;
- if(!conn->bits.tls_enable_alpn)
- goto out;
- if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) {
- result = Curl_conn_may_http3(data, conn);
- if(result) /* can't do it */
- goto out;
- try_h3 = TRUE;
- try_h21 = FALSE;
- }
- else if(data->state.httpwant >= CURL_HTTP_VERSION_3) {
- /* We assume that silently not even trying H3 is ok here */
- /* TODO: should we fail instead? */
- try_h3 = (Curl_conn_may_http3(data, conn) == CURLE_OK);
- try_h21 = TRUE;
- }
- result = cf_http_connect_add(data, conn, sockindex, remotehost,
- try_h3, try_h21);
- out:
- return result;
- }
- #endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */
|