Path: blob/master/Utilities/cmcurl/lib/cf-https-connect.c
5003 views
/***************************************************************************1* _ _ ____ _2* Project ___| | | | _ \| |3* / __| | | | |_) | |4* | (__| |_| | _ <| |___5* \___|\___/|_| \_\_____|6*7* Copyright (C) Daniel Stenberg, <[email protected]>, et al.8*9* This software is licensed as described in the file COPYING, which10* you should have received as part of this distribution. The terms11* are also available at https://curl.se/docs/copyright.html.12*13* You may opt to use, copy, modify, merge, publish, distribute and/or sell14* copies of the Software, and permit persons to whom the Software is15* furnished to do so, under the terms of the COPYING file.16*17* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY18* KIND, either express or implied.19*20* SPDX-License-Identifier: curl21*22***************************************************************************/23#include "curl_setup.h"2425#ifndef CURL_DISABLE_HTTP2627#include "urldata.h"28#include "curl_trc.h"29#include "cfilters.h"30#include "connect.h"31#include "hostip.h"32#include "multiif.h"33#include "cf-https-connect.h"34#include "http2.h"35#include "progress.h"36#include "select.h"37#include "vquic/vquic.h"3839typedef enum {40CF_HC_INIT,41CF_HC_CONNECT,42CF_HC_SUCCESS,43CF_HC_FAILURE44} cf_hc_state;4546struct cf_hc_baller {47const char *name;48struct Curl_cfilter *cf;49CURLcode result;50struct curltime started;51int reply_ms;52uint8_t transport;53enum alpnid alpn_id;54BIT(shutdown);55};5657static void cf_hc_baller_reset(struct cf_hc_baller *b,58struct Curl_easy *data)59{60if(b->cf) {61Curl_conn_cf_close(b->cf, data);62Curl_conn_cf_discard_chain(&b->cf, data);63b->cf = NULL;64}65b->result = CURLE_OK;66b->reply_ms = -1;67}6869static bool cf_hc_baller_is_active(struct cf_hc_baller *b)70{71return b->cf && !b->result;72}7374static bool cf_hc_baller_has_started(struct cf_hc_baller *b)75{76return !!b->cf;77}7879static int cf_hc_baller_reply_ms(struct cf_hc_baller *b,80struct Curl_easy *data)81{82if(b->cf && (b->reply_ms < 0))83b->cf->cft->query(b->cf, data, CF_QUERY_CONNECT_REPLY_MS,84&b->reply_ms, NULL);85return b->reply_ms;86}8788static bool cf_hc_baller_data_pending(struct cf_hc_baller *b,89const struct Curl_easy *data)90{91return b->cf && !b->result && b->cf->cft->has_data_pending(b->cf, data);92}9394static bool cf_hc_baller_needs_flush(struct cf_hc_baller *b,95struct Curl_easy *data)96{97return b->cf && !b->result && Curl_conn_cf_needs_flush(b->cf, data);98}99100static CURLcode cf_hc_baller_cntrl(struct cf_hc_baller *b,101struct Curl_easy *data,102int event, int arg1, void *arg2)103{104if(b->cf && !b->result)105return Curl_conn_cf_cntrl(b->cf, data, FALSE, event, arg1, arg2);106return CURLE_OK;107}108109struct cf_hc_ctx {110cf_hc_state state;111struct curltime started; /* when connect started */112CURLcode result; /* overall result */113struct cf_hc_baller ballers[2];114size_t baller_count;115timediff_t soft_eyeballs_timeout_ms;116timediff_t hard_eyeballs_timeout_ms;117};118119static void cf_hc_baller_assign(struct cf_hc_baller *b,120enum alpnid alpn_id,121uint8_t def_transport)122{123b->alpn_id = alpn_id;124b->transport = def_transport;125switch(b->alpn_id) {126case ALPN_h3:127b->name = "h3";128b->transport = TRNSPRT_QUIC;129break;130case ALPN_h2:131b->name = "h2";132break;133case ALPN_h1:134b->name = "h1";135break;136default:137b->result = CURLE_FAILED_INIT;138break;139}140}141142static void cf_hc_baller_init(struct cf_hc_baller *b,143struct Curl_cfilter *cf,144struct Curl_easy *data,145uint8_t transport)146{147struct Curl_cfilter *save = cf->next;148149cf->next = NULL;150b->started = *Curl_pgrs_now(data);151switch(b->alpn_id) {152case ALPN_h3:153transport = TRNSPRT_QUIC;154break;155default:156break;157}158159if(!b->result)160b->result = Curl_cf_setup_insert_after(cf, data, transport,161CURL_CF_SSL_ENABLE);162b->cf = cf->next;163cf->next = save;164}165166static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b,167struct Curl_cfilter *cf,168struct Curl_easy *data,169bool *done)170{171struct Curl_cfilter *save = cf->next;172173cf->next = b->cf;174b->result = Curl_conn_cf_connect(cf->next, data, done);175b->cf = cf->next; /* it might mutate */176cf->next = save;177return b->result;178}179180static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data)181{182struct cf_hc_ctx *ctx = cf->ctx;183size_t i;184185if(ctx) {186for(i = 0; i < ctx->baller_count; ++i)187cf_hc_baller_reset(&ctx->ballers[i], data);188ctx->state = CF_HC_INIT;189ctx->result = CURLE_OK;190ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout;191ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 4;192}193}194195static CURLcode baller_connected(struct Curl_cfilter *cf,196struct Curl_easy *data,197struct cf_hc_baller *winner)198{199struct cf_hc_ctx *ctx = cf->ctx;200CURLcode result = CURLE_OK;201int reply_ms;202size_t i;203204DEBUGASSERT(winner->cf);205for(i = 0; i < ctx->baller_count; ++i)206if(winner != &ctx->ballers[i])207cf_hc_baller_reset(&ctx->ballers[i], data);208209reply_ms = cf_hc_baller_reply_ms(winner, data);210if(reply_ms >= 0)211CURL_TRC_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms",212winner->name,213(int)curlx_ptimediff_ms(Curl_pgrs_now(data),214&winner->started), reply_ms);215else216CURL_TRC_CF(data, cf, "deferred handshake %s: %dms",217winner->name, (int)curlx_ptimediff_ms(Curl_pgrs_now(data),218&winner->started));219220/* install the winning filter below this one. */221cf->next = winner->cf;222winner->cf = NULL;223224#ifdef USE_NGHTTP2225{226/* Using nghttp2, we add the filter "below" us, so when the conn227* closes, we tear it down for a fresh reconnect */228const char *alpn = Curl_conn_cf_get_alpn_negotiated(cf->next, data);229if(alpn && !strcmp("h2", alpn)) {230result = Curl_http2_switch_at(cf, data);231if(result) {232ctx->state = CF_HC_FAILURE;233ctx->result = result;234return result;235}236}237}238#endif239240ctx->state = CF_HC_SUCCESS;241cf->connected = TRUE;242return result;243}244245static bool time_to_start_next(struct Curl_cfilter *cf,246struct Curl_easy *data,247size_t idx, struct curltime now)248{249struct cf_hc_ctx *ctx = cf->ctx;250timediff_t elapsed_ms;251size_t i;252253if(idx >= ctx->baller_count)254return FALSE;255if(cf_hc_baller_has_started(&ctx->ballers[idx]))256return FALSE;257for(i = 0; i < idx; i++) {258if(!ctx->ballers[i].result)259break;260}261if(i == idx) {262CURL_TRC_CF(data, cf, "all previous attempts failed, starting %s",263ctx->ballers[idx].name);264return TRUE;265}266elapsed_ms = curlx_ptimediff_ms(&now, &ctx->started);267if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) {268CURL_TRC_CF(data, cf, "hard timeout of %" FMT_TIMEDIFF_T "ms reached, "269"starting %s",270ctx->hard_eyeballs_timeout_ms, ctx->ballers[idx].name);271return TRUE;272}273274if((idx > 0) && (elapsed_ms >= ctx->soft_eyeballs_timeout_ms)) {275if(cf_hc_baller_reply_ms(&ctx->ballers[idx - 1], data) < 0) {276CURL_TRC_CF(data, cf, "soft timeout of %" FMT_TIMEDIFF_T "ms reached, "277"%s has not seen any data, starting %s",278ctx->soft_eyeballs_timeout_ms,279ctx->ballers[idx - 1].name, ctx->ballers[idx].name);280return TRUE;281}282/* set the effective hard timeout again */283Curl_expire(data, ctx->hard_eyeballs_timeout_ms - elapsed_ms,284EXPIRE_ALPN_EYEBALLS);285}286return FALSE;287}288289static CURLcode cf_hc_connect(struct Curl_cfilter *cf,290struct Curl_easy *data,291bool *done)292{293struct cf_hc_ctx *ctx = cf->ctx;294CURLcode result = CURLE_OK;295size_t i, failed_ballers;296297if(cf->connected) {298*done = TRUE;299return CURLE_OK;300}301302*done = FALSE;303switch(ctx->state) {304case CF_HC_INIT:305DEBUGASSERT(!cf->next);306for(i = 0; i < ctx->baller_count; i++)307DEBUGASSERT(!ctx->ballers[i].cf);308CURL_TRC_CF(data, cf, "connect, init");309ctx->started = *Curl_pgrs_now(data);310cf_hc_baller_init(&ctx->ballers[0], cf, data, ctx->ballers[0].transport);311if(ctx->baller_count > 1) {312Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS);313CURL_TRC_CF(data, cf, "set next attempt to start in %" FMT_TIMEDIFF_T314"ms", ctx->soft_eyeballs_timeout_ms);315}316ctx->state = CF_HC_CONNECT;317FALLTHROUGH();318319case CF_HC_CONNECT:320if(cf_hc_baller_is_active(&ctx->ballers[0])) {321result = cf_hc_baller_connect(&ctx->ballers[0], cf, data, done);322if(!result && *done) {323result = baller_connected(cf, data, &ctx->ballers[0]);324goto out;325}326}327328if(time_to_start_next(cf, data, 1, *Curl_pgrs_now(data))) {329cf_hc_baller_init(&ctx->ballers[1], cf, data, ctx->ballers[1].transport);330}331332if((ctx->baller_count > 1) && cf_hc_baller_is_active(&ctx->ballers[1])) {333CURL_TRC_CF(data, cf, "connect, check %s", ctx->ballers[1].name);334result = cf_hc_baller_connect(&ctx->ballers[1], cf, data, done);335if(!result && *done) {336result = baller_connected(cf, data, &ctx->ballers[1]);337goto out;338}339}340341failed_ballers = 0;342for(i = 0; i < ctx->baller_count; i++) {343if(ctx->ballers[i].result)344++failed_ballers;345}346347if(failed_ballers == ctx->baller_count) {348/* all have failed. we give up */349CURL_TRC_CF(data, cf, "connect, all attempts failed");350for(i = 0; i < ctx->baller_count; i++) {351if(ctx->ballers[i].result) {352result = ctx->ballers[i].result;353break;354}355}356ctx->state = CF_HC_FAILURE;357goto out;358}359result = CURLE_OK;360*done = FALSE;361break;362363case CF_HC_FAILURE:364result = ctx->result;365cf->connected = FALSE;366*done = FALSE;367break;368369case CF_HC_SUCCESS:370result = CURLE_OK;371cf->connected = TRUE;372*done = TRUE;373break;374}375376out:377CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done);378return result;379}380381static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf,382struct Curl_easy *data, bool *done)383{384struct cf_hc_ctx *ctx = cf->ctx;385size_t i;386CURLcode result = CURLE_OK;387388DEBUGASSERT(data);389if(cf->connected) {390*done = TRUE;391return CURLE_OK;392}393394/* shutdown all ballers that have not done so already. If one fails,395* continue shutting down others until all are shutdown. */396for(i = 0; i < ctx->baller_count; i++) {397struct cf_hc_baller *b = &ctx->ballers[i];398bool bdone = FALSE;399if(!cf_hc_baller_is_active(b) || b->shutdown)400continue;401b->result = b->cf->cft->do_shutdown(b->cf, data, &bdone);402if(b->result || bdone)403b->shutdown = TRUE; /* treat a failed shutdown as done */404}405406*done = TRUE;407for(i = 0; i < ctx->baller_count; i++) {408if(!ctx->ballers[i].shutdown)409*done = FALSE;410}411if(*done) {412for(i = 0; i < ctx->baller_count; i++) {413if(ctx->ballers[i].result)414result = ctx->ballers[i].result;415}416}417CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done);418return result;419}420421static CURLcode cf_hc_adjust_pollset(struct Curl_cfilter *cf,422struct Curl_easy *data,423struct easy_pollset *ps)424{425CURLcode result = CURLE_OK;426if(!cf->connected) {427struct cf_hc_ctx *ctx = cf->ctx;428size_t i;429430for(i = 0; (i < ctx->baller_count) && !result; i++) {431struct cf_hc_baller *b = &ctx->ballers[i];432if(!cf_hc_baller_is_active(b))433continue;434result = Curl_conn_cf_adjust_pollset(b->cf, data, ps);435}436CURL_TRC_CF(data, cf, "adjust_pollset -> %d, %d socks", result, ps->n);437}438return result;439}440441static bool cf_hc_data_pending(struct Curl_cfilter *cf,442const struct Curl_easy *data)443{444struct cf_hc_ctx *ctx = cf->ctx;445size_t i;446447if(cf->connected)448return cf->next->cft->has_data_pending(cf->next, data);449450for(i = 0; i < ctx->baller_count; i++)451if(cf_hc_baller_data_pending(&ctx->ballers[i], data))452return TRUE;453return FALSE;454}455456static struct curltime cf_get_max_baller_time(struct Curl_cfilter *cf,457struct Curl_easy *data,458int query)459{460struct cf_hc_ctx *ctx = cf->ctx;461struct curltime t, tmax;462size_t i;463464memset(&tmax, 0, sizeof(tmax));465for(i = 0; i < ctx->baller_count; i++) {466struct Curl_cfilter *cfb = ctx->ballers[i].cf;467memset(&t, 0, sizeof(t));468if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {469if((t.tv_sec || t.tv_usec) && curlx_ptimediff_us(&t, &tmax) > 0)470tmax = t;471}472}473return tmax;474}475476static CURLcode cf_hc_query(struct Curl_cfilter *cf,477struct Curl_easy *data,478int query, int *pres1, void *pres2)479{480struct cf_hc_ctx *ctx = cf->ctx;481size_t i;482483if(!cf->connected) {484switch(query) {485case CF_QUERY_TIMER_CONNECT: {486struct curltime *when = pres2;487*when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT);488return CURLE_OK;489}490case CF_QUERY_TIMER_APPCONNECT: {491struct curltime *when = pres2;492*when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT);493return CURLE_OK;494}495case CF_QUERY_NEED_FLUSH: {496for(i = 0; i < ctx->baller_count; i++)497if(cf_hc_baller_needs_flush(&ctx->ballers[i], data)) {498*pres1 = TRUE;499return CURLE_OK;500}501break;502}503default:504break;505}506}507return cf->next ?508cf->next->cft->query(cf->next, data, query, pres1, pres2) :509CURLE_UNKNOWN_OPTION;510}511512static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf,513struct Curl_easy *data,514int event, int arg1, void *arg2)515{516struct cf_hc_ctx *ctx = cf->ctx;517CURLcode result = CURLE_OK;518size_t i;519520if(!cf->connected) {521for(i = 0; i < ctx->baller_count; i++) {522result = cf_hc_baller_cntrl(&ctx->ballers[i], data, event, arg1, arg2);523if(result && (result != CURLE_AGAIN))524goto out;525}526result = CURLE_OK;527}528out:529return result;530}531532static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data)533{534CURL_TRC_CF(data, cf, "close");535cf_hc_reset(cf, data);536cf->connected = FALSE;537538if(cf->next) {539cf->next->cft->do_close(cf->next, data);540Curl_conn_cf_discard_chain(&cf->next, data);541}542}543544static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)545{546struct cf_hc_ctx *ctx = cf->ctx;547548(void)data;549CURL_TRC_CF(data, cf, "destroy");550cf_hc_reset(cf, data);551Curl_safefree(ctx);552}553554struct Curl_cftype Curl_cft_http_connect = {555"HTTPS-CONNECT",5560,557CURL_LOG_LVL_NONE,558cf_hc_destroy,559cf_hc_connect,560cf_hc_close,561cf_hc_shutdown,562cf_hc_adjust_pollset,563cf_hc_data_pending,564Curl_cf_def_send,565Curl_cf_def_recv,566cf_hc_cntrl,567Curl_cf_def_conn_is_alive,568Curl_cf_def_conn_keep_alive,569cf_hc_query,570};571572static CURLcode cf_hc_create(struct Curl_cfilter **pcf,573struct Curl_easy *data,574enum alpnid *alpnids, size_t alpn_count,575uint8_t def_transport)576{577struct Curl_cfilter *cf = NULL;578struct cf_hc_ctx *ctx;579CURLcode result = CURLE_OK;580size_t i;581582ctx = curlx_calloc(1, sizeof(*ctx));583if(!ctx) {584result = CURLE_OUT_OF_MEMORY;585goto out;586}587588DEBUGASSERT(alpnids);589DEBUGASSERT(alpn_count);590DEBUGASSERT(alpn_count <= CURL_ARRAYSIZE(ctx->ballers));591if(!alpn_count || (alpn_count > CURL_ARRAYSIZE(ctx->ballers))) {592failf(data, "https-connect filter create with unsupported %zu ALPN ids",593alpn_count);594result = CURLE_FAILED_INIT;595goto out;596}597598for(i = 0; i < alpn_count; ++i)599cf_hc_baller_assign(&ctx->ballers[i], alpnids[i], def_transport);600for(; i < CURL_ARRAYSIZE(ctx->ballers); ++i)601ctx->ballers[i].alpn_id = ALPN_none;602ctx->baller_count = alpn_count;603604result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx);605if(result)606goto out;607ctx = NULL;608cf_hc_reset(cf, data);609610out:611*pcf = result ? NULL : cf;612curlx_free(ctx);613return result;614}615616static CURLcode cf_http_connect_add(struct Curl_easy *data,617struct connectdata *conn,618int sockindex,619enum alpnid *alpn_ids, size_t alpn_count,620uint8_t def_transport)621{622struct Curl_cfilter *cf;623CURLcode result = CURLE_OK;624625DEBUGASSERT(data);626result = cf_hc_create(&cf, data, alpn_ids, alpn_count, def_transport);627if(result)628goto out;629Curl_conn_cf_add(data, conn, sockindex, cf);630out:631return result;632}633634static bool cf_https_alpns_contain(enum alpnid id,635enum alpnid *list, size_t len)636{637size_t i;638for(i = 0; i < len; ++i) {639if(id == list[i])640return TRUE;641}642return FALSE;643}644645CURLcode Curl_cf_https_setup(struct Curl_easy *data,646struct connectdata *conn,647int sockindex)648{649enum alpnid alpn_ids[2];650size_t alpn_count = 0;651CURLcode result = CURLE_OK;652struct Curl_cfilter cf_fake, *cf = NULL;653654(void)sockindex;655/* we want to log for the filter before we create it, fake it. */656memset(&cf_fake, 0, sizeof(cf_fake));657cf_fake.cft = &Curl_cft_http_connect;658cf = &cf_fake;659660if(conn->bits.tls_enable_alpn) {661#ifdef USE_HTTPSRR662/* Is there an HTTPSRR use its ALPNs here.663* We are here after having selected a connection to a host+port and664* can no longer change that. Any HTTPSRR advice for other hosts and ports665* we need to ignore. */666struct Curl_dns_entry *dns = data->state.dns[sockindex];667struct Curl_https_rrinfo *rr = dns ? dns->hinfo : NULL;668if(rr && !rr->no_def_alpn && /* ALPNs are defaults */669(!rr->target || /* for same host */670!rr->target[0] ||671(rr->target[0] == '.' &&672!rr->target[1])) &&673(rr->port < 0 || /* for same port */674rr->port == conn->remote_port)) {675size_t i;676for(i = 0; i < CURL_ARRAYSIZE(rr->alpns) &&677alpn_count < CURL_ARRAYSIZE(alpn_ids); ++i) {678enum alpnid alpn = rr->alpns[i];679if(cf_https_alpns_contain(alpn, alpn_ids, alpn_count))680continue;681switch(alpn) {682case ALPN_h3:683if(Curl_conn_may_http3(data, conn, conn->transport_wanted))684break; /* not possible */685if(data->state.http_neg.allowed & CURL_HTTP_V3x) {686CURL_TRC_CF(data, cf, "adding h3 via HTTPS-RR");687alpn_ids[alpn_count++] = alpn;688}689break;690case ALPN_h2:691if(data->state.http_neg.allowed & CURL_HTTP_V2x) {692CURL_TRC_CF(data, cf, "adding h2 via HTTPS-RR");693alpn_ids[alpn_count++] = alpn;694}695break;696case ALPN_h1:697if(data->state.http_neg.allowed & CURL_HTTP_V1x) {698CURL_TRC_CF(data, cf, "adding h1 via HTTPS-RR");699alpn_ids[alpn_count++] = alpn;700}701break;702default: /* ignore */703break;704}705}706}707#endif708709/* Add preferred HTTP version ALPN first */710if(data->state.http_neg.preferred &&711(alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&712(data->state.http_neg.preferred & data->state.http_neg.allowed)) {713enum alpnid alpn_pref = ALPN_none;714switch(data->state.http_neg.preferred) {715case CURL_HTTP_V3x:716if(!Curl_conn_may_http3(data, conn, conn->transport_wanted))717alpn_pref = ALPN_h3;718break;719case CURL_HTTP_V2x:720alpn_pref = ALPN_h2;721break;722case CURL_HTTP_V1x:723alpn_pref = ALPN_h1;724break;725default:726break;727}728if(alpn_pref &&729!cf_https_alpns_contain(alpn_pref, alpn_ids, alpn_count)) {730alpn_ids[alpn_count++] = alpn_pref;731}732}733734if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&735(data->state.http_neg.wanted & CURL_HTTP_V3x) &&736!cf_https_alpns_contain(ALPN_h3, alpn_ids, alpn_count)) {737result = Curl_conn_may_http3(data, conn, conn->transport_wanted);738if(!result) {739CURL_TRC_CF(data, cf, "adding wanted h3");740alpn_ids[alpn_count++] = ALPN_h3;741}742else if(data->state.http_neg.wanted == CURL_HTTP_V3x)743goto out; /* only h3 allowed, not possible, error out */744}745if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&746(data->state.http_neg.wanted & CURL_HTTP_V2x) &&747!cf_https_alpns_contain(ALPN_h2, alpn_ids, alpn_count)) {748CURL_TRC_CF(data, cf, "adding wanted h2");749alpn_ids[alpn_count++] = ALPN_h2;750}751else if((alpn_count < CURL_ARRAYSIZE(alpn_ids)) &&752(data->state.http_neg.wanted & CURL_HTTP_V1x) &&753!cf_https_alpns_contain(ALPN_h1, alpn_ids, alpn_count)) {754CURL_TRC_CF(data, cf, "adding wanted h1");755alpn_ids[alpn_count++] = ALPN_h1;756}757}758759/* If we identified ALPNs to use, install our filter. Otherwise,760* install nothing, so our call will use a default connect setup. */761if(alpn_count) {762result = cf_http_connect_add(data, conn, sockindex,763alpn_ids, alpn_count,764conn->transport_wanted);765}766767out:768return result;769}770771#endif /* !CURL_DISABLE_HTTP */772773774