1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25 #include "curl_setup.h"
26
27 #if !defined(CURL_DISABLE_HTTP)
28
29 #include "urldata.h"
30 #include <curl/curl.h>
31 #include "curl_trc.h"
32 #include "cfilters.h"
33 #include "connect.h"
34 #include "multiif.h"
35 #include "cf-https-connect.h"
36 #include "http2.h"
37 #include "vquic/vquic.h"
38
39 /* The last 3 #include files should be in this order */
40 #include "curl_printf.h"
41 #include "curl_memory.h"
42 #include "memdebug.h"
43
44
45 typedef enum {
46 CF_HC_INIT,
47 CF_HC_CONNECT,
48 CF_HC_SUCCESS,
49 CF_HC_FAILURE
50 } cf_hc_state;
51
52 struct cf_hc_baller {
53 const char *name;
54 struct Curl_cfilter *cf;
55 CURLcode result;
56 struct curltime started;
57 int reply_ms;
58 BIT(enabled);
59 BIT(shutdown);
60 };
61
cf_hc_baller_reset(struct cf_hc_baller * b,struct Curl_easy * data)62 static void cf_hc_baller_reset(struct cf_hc_baller *b,
63 struct Curl_easy *data)
64 {
65 if(b->cf) {
66 Curl_conn_cf_close(b->cf, data);
67 Curl_conn_cf_discard_chain(&b->cf, data);
68 b->cf = NULL;
69 }
70 b->result = CURLE_OK;
71 b->reply_ms = -1;
72 }
73
cf_hc_baller_is_active(struct cf_hc_baller * b)74 static bool cf_hc_baller_is_active(struct cf_hc_baller *b)
75 {
76 return b->enabled && b->cf && !b->result;
77 }
78
cf_hc_baller_has_started(struct cf_hc_baller * b)79 static bool cf_hc_baller_has_started(struct cf_hc_baller *b)
80 {
81 return !!b->cf;
82 }
83
cf_hc_baller_reply_ms(struct cf_hc_baller * b,struct Curl_easy * data)84 static int cf_hc_baller_reply_ms(struct cf_hc_baller *b,
85 struct Curl_easy *data)
86 {
87 if(b->reply_ms < 0)
88 b->cf->cft->query(b->cf, data, CF_QUERY_CONNECT_REPLY_MS,
89 &b->reply_ms, NULL);
90 return b->reply_ms;
91 }
92
cf_hc_baller_data_pending(struct cf_hc_baller * b,const struct Curl_easy * data)93 static bool cf_hc_baller_data_pending(struct cf_hc_baller *b,
94 const struct Curl_easy *data)
95 {
96 return b->cf && !b->result && b->cf->cft->has_data_pending(b->cf, data);
97 }
98
cf_hc_baller_needs_flush(struct cf_hc_baller * b,struct Curl_easy * data)99 static bool cf_hc_baller_needs_flush(struct cf_hc_baller *b,
100 struct Curl_easy *data)
101 {
102 return b->cf && !b->result && Curl_conn_cf_needs_flush(b->cf, data);
103 }
104
cf_hc_baller_cntrl(struct cf_hc_baller * b,struct Curl_easy * data,int event,int arg1,void * arg2)105 static CURLcode cf_hc_baller_cntrl(struct cf_hc_baller *b,
106 struct Curl_easy *data,
107 int event, int arg1, void *arg2)
108 {
109 if(b->cf && !b->result)
110 return Curl_conn_cf_cntrl(b->cf, data, FALSE, event, arg1, arg2);
111 return CURLE_OK;
112 }
113
114 struct cf_hc_ctx {
115 cf_hc_state state;
116 const struct Curl_dns_entry *remotehost;
117 struct curltime started; /* when connect started */
118 CURLcode result; /* overall result */
119 struct cf_hc_baller h3_baller;
120 struct cf_hc_baller h21_baller;
121 unsigned int soft_eyeballs_timeout_ms;
122 unsigned int hard_eyeballs_timeout_ms;
123 };
124
cf_hc_baller_init(struct cf_hc_baller * b,struct Curl_cfilter * cf,struct Curl_easy * data,const char * name,int transport)125 static void cf_hc_baller_init(struct cf_hc_baller *b,
126 struct Curl_cfilter *cf,
127 struct Curl_easy *data,
128 const char *name,
129 int transport)
130 {
131 struct cf_hc_ctx *ctx = cf->ctx;
132 struct Curl_cfilter *save = cf->next;
133
134 b->name = name;
135 cf->next = NULL;
136 b->started = Curl_now();
137 b->result = Curl_cf_setup_insert_after(cf, data, ctx->remotehost,
138 transport, CURL_CF_SSL_ENABLE);
139 b->cf = cf->next;
140 cf->next = save;
141 }
142
cf_hc_baller_connect(struct cf_hc_baller * b,struct Curl_cfilter * cf,struct Curl_easy * data,bool * done)143 static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b,
144 struct Curl_cfilter *cf,
145 struct Curl_easy *data,
146 bool *done)
147 {
148 struct Curl_cfilter *save = cf->next;
149
150 cf->next = b->cf;
151 b->result = Curl_conn_cf_connect(cf->next, data, FALSE, done);
152 b->cf = cf->next; /* it might mutate */
153 cf->next = save;
154 return b->result;
155 }
156
cf_hc_reset(struct Curl_cfilter * cf,struct Curl_easy * data)157 static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data)
158 {
159 struct cf_hc_ctx *ctx = cf->ctx;
160
161 if(ctx) {
162 cf_hc_baller_reset(&ctx->h3_baller, data);
163 cf_hc_baller_reset(&ctx->h21_baller, data);
164 ctx->state = CF_HC_INIT;
165 ctx->result = CURLE_OK;
166 ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout;
167 ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2;
168 }
169 }
170
baller_connected(struct Curl_cfilter * cf,struct Curl_easy * data,struct cf_hc_baller * winner)171 static CURLcode baller_connected(struct Curl_cfilter *cf,
172 struct Curl_easy *data,
173 struct cf_hc_baller *winner)
174 {
175 struct cf_hc_ctx *ctx = cf->ctx;
176 CURLcode result = CURLE_OK;
177 int reply_ms;
178
179 DEBUGASSERT(winner->cf);
180 if(winner != &ctx->h3_baller)
181 cf_hc_baller_reset(&ctx->h3_baller, data);
182 if(winner != &ctx->h21_baller)
183 cf_hc_baller_reset(&ctx->h21_baller, data);
184
185 reply_ms = cf_hc_baller_reply_ms(winner, data);
186 if(reply_ms >= 0)
187 CURL_TRC_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms",
188 winner->name, (int)Curl_timediff(Curl_now(), winner->started),
189 reply_ms);
190 else
191 CURL_TRC_CF(data, cf, "deferred handshake %s: %dms",
192 winner->name, (int)Curl_timediff(Curl_now(), winner->started));
193
194 cf->next = winner->cf;
195 winner->cf = NULL;
196
197 switch(cf->conn->alpn) {
198 case CURL_HTTP_VERSION_3:
199 break;
200 case CURL_HTTP_VERSION_2:
201 #ifdef USE_NGHTTP2
202 /* Using nghttp2, we add the filter "below" us, so when the conn
203 * closes, we tear it down for a fresh reconnect */
204 result = Curl_http2_switch_at(cf, data);
205 if(result) {
206 ctx->state = CF_HC_FAILURE;
207 ctx->result = result;
208 return result;
209 }
210 #endif
211 break;
212 default:
213 break;
214 }
215 ctx->state = CF_HC_SUCCESS;
216 cf->connected = TRUE;
217 return result;
218 }
219
220
time_to_start_h21(struct Curl_cfilter * cf,struct Curl_easy * data,struct curltime now)221 static bool time_to_start_h21(struct Curl_cfilter *cf,
222 struct Curl_easy *data,
223 struct curltime now)
224 {
225 struct cf_hc_ctx *ctx = cf->ctx;
226 timediff_t elapsed_ms;
227
228 if(!ctx->h21_baller.enabled || cf_hc_baller_has_started(&ctx->h21_baller))
229 return FALSE;
230
231 if(!ctx->h3_baller.enabled || !cf_hc_baller_is_active(&ctx->h3_baller))
232 return TRUE;
233
234 elapsed_ms = Curl_timediff(now, ctx->started);
235 if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) {
236 CURL_TRC_CF(data, cf, "hard timeout of %dms reached, starting h21",
237 ctx->hard_eyeballs_timeout_ms);
238 return TRUE;
239 }
240
241 if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) {
242 if(cf_hc_baller_reply_ms(&ctx->h3_baller, data) < 0) {
243 CURL_TRC_CF(data, cf, "soft timeout of %dms reached, h3 has not "
244 "seen any data, starting h21",
245 ctx->soft_eyeballs_timeout_ms);
246 return TRUE;
247 }
248 /* set the effective hard timeout again */
249 Curl_expire(data, ctx->hard_eyeballs_timeout_ms - elapsed_ms,
250 EXPIRE_ALPN_EYEBALLS);
251 }
252 return FALSE;
253 }
254
cf_hc_connect(struct Curl_cfilter * cf,struct Curl_easy * data,bool blocking,bool * done)255 static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
256 struct Curl_easy *data,
257 bool blocking, bool *done)
258 {
259 struct cf_hc_ctx *ctx = cf->ctx;
260 struct curltime now;
261 CURLcode result = CURLE_OK;
262
263 (void)blocking;
264 if(cf->connected) {
265 *done = TRUE;
266 return CURLE_OK;
267 }
268
269 *done = FALSE;
270 now = Curl_now();
271 switch(ctx->state) {
272 case CF_HC_INIT:
273 DEBUGASSERT(!ctx->h3_baller.cf);
274 DEBUGASSERT(!ctx->h21_baller.cf);
275 DEBUGASSERT(!cf->next);
276 CURL_TRC_CF(data, cf, "connect, init");
277 ctx->started = now;
278 if(ctx->h3_baller.enabled) {
279 cf_hc_baller_init(&ctx->h3_baller, cf, data, "h3", TRNSPRT_QUIC);
280 if(ctx->h21_baller.enabled)
281 Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS);
282 }
283 else if(ctx->h21_baller.enabled)
284 cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21",
285 cf->conn->transport);
286 ctx->state = CF_HC_CONNECT;
287 FALLTHROUGH();
288
289 case CF_HC_CONNECT:
290 if(cf_hc_baller_is_active(&ctx->h3_baller)) {
291 result = cf_hc_baller_connect(&ctx->h3_baller, cf, data, done);
292 if(!result && *done) {
293 result = baller_connected(cf, data, &ctx->h3_baller);
294 goto out;
295 }
296 }
297
298 if(time_to_start_h21(cf, data, now)) {
299 cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21",
300 cf->conn->transport);
301 }
302
303 if(cf_hc_baller_is_active(&ctx->h21_baller)) {
304 CURL_TRC_CF(data, cf, "connect, check h21");
305 result = cf_hc_baller_connect(&ctx->h21_baller, cf, data, done);
306 if(!result && *done) {
307 result = baller_connected(cf, data, &ctx->h21_baller);
308 goto out;
309 }
310 }
311
312 if((!ctx->h3_baller.enabled || ctx->h3_baller.result) &&
313 (!ctx->h21_baller.enabled || ctx->h21_baller.result)) {
314 /* both failed or disabled. we give up */
315 CURL_TRC_CF(data, cf, "connect, all failed");
316 result = ctx->result = ctx->h3_baller.enabled ?
317 ctx->h3_baller.result : ctx->h21_baller.result;
318 ctx->state = CF_HC_FAILURE;
319 goto out;
320 }
321 result = CURLE_OK;
322 *done = FALSE;
323 break;
324
325 case CF_HC_FAILURE:
326 result = ctx->result;
327 cf->connected = FALSE;
328 *done = FALSE;
329 break;
330
331 case CF_HC_SUCCESS:
332 result = CURLE_OK;
333 cf->connected = TRUE;
334 *done = TRUE;
335 break;
336 }
337
338 out:
339 CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done);
340 return result;
341 }
342
cf_hc_shutdown(struct Curl_cfilter * cf,struct Curl_easy * data,bool * done)343 static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf,
344 struct Curl_easy *data, bool *done)
345 {
346 struct cf_hc_ctx *ctx = cf->ctx;
347 struct cf_hc_baller *ballers[2];
348 size_t i;
349 CURLcode result = CURLE_OK;
350
351 DEBUGASSERT(data);
352 if(cf->connected) {
353 *done = TRUE;
354 return CURLE_OK;
355 }
356
357 /* shutdown all ballers that have not done so already. If one fails,
358 * continue shutting down others until all are shutdown. */
359 ballers[0] = &ctx->h3_baller;
360 ballers[1] = &ctx->h21_baller;
361 for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
362 struct cf_hc_baller *b = ballers[i];
363 bool bdone = FALSE;
364 if(!cf_hc_baller_is_active(b) || b->shutdown)
365 continue;
366 b->result = b->cf->cft->do_shutdown(b->cf, data, &bdone);
367 if(b->result || bdone)
368 b->shutdown = TRUE; /* treat a failed shutdown as done */
369 }
370
371 *done = TRUE;
372 for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
373 if(ballers[i] && !ballers[i]->shutdown)
374 *done = FALSE;
375 }
376 if(*done) {
377 for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
378 if(ballers[i] && ballers[i]->result)
379 result = ballers[i]->result;
380 }
381 }
382 CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done);
383 return result;
384 }
385
cf_hc_adjust_pollset(struct Curl_cfilter * cf,struct Curl_easy * data,struct easy_pollset * ps)386 static void cf_hc_adjust_pollset(struct Curl_cfilter *cf,
387 struct Curl_easy *data,
388 struct easy_pollset *ps)
389 {
390 if(!cf->connected) {
391 struct cf_hc_ctx *ctx = cf->ctx;
392 struct cf_hc_baller *ballers[2];
393 size_t i;
394
395 ballers[0] = &ctx->h3_baller;
396 ballers[1] = &ctx->h21_baller;
397 for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
398 struct cf_hc_baller *b = ballers[i];
399 if(!cf_hc_baller_is_active(b))
400 continue;
401 Curl_conn_cf_adjust_pollset(b->cf, data, ps);
402 }
403 CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num);
404 }
405 }
406
cf_hc_data_pending(struct Curl_cfilter * cf,const struct Curl_easy * data)407 static bool cf_hc_data_pending(struct Curl_cfilter *cf,
408 const struct Curl_easy *data)
409 {
410 struct cf_hc_ctx *ctx = cf->ctx;
411
412 if(cf->connected)
413 return cf->next->cft->has_data_pending(cf->next, data);
414
415 CURL_TRC_CF((struct Curl_easy *)data, cf, "data_pending");
416 return cf_hc_baller_data_pending(&ctx->h3_baller, data)
417 || cf_hc_baller_data_pending(&ctx->h21_baller, data);
418 }
419
cf_get_max_baller_time(struct Curl_cfilter * cf,struct Curl_easy * data,int query)420 static struct curltime cf_get_max_baller_time(struct Curl_cfilter *cf,
421 struct Curl_easy *data,
422 int query)
423 {
424 struct cf_hc_ctx *ctx = cf->ctx;
425 struct Curl_cfilter *cfb;
426 struct curltime t, tmax;
427
428 memset(&tmax, 0, sizeof(tmax));
429 memset(&t, 0, sizeof(t));
430 cfb = ctx->h21_baller.enabled ? ctx->h21_baller.cf : NULL;
431 if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {
432 if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0)
433 tmax = t;
434 }
435 memset(&t, 0, sizeof(t));
436 cfb = ctx->h3_baller.enabled ? ctx->h3_baller.cf : NULL;
437 if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {
438 if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0)
439 tmax = t;
440 }
441 return tmax;
442 }
443
cf_hc_query(struct Curl_cfilter * cf,struct Curl_easy * data,int query,int * pres1,void * pres2)444 static CURLcode cf_hc_query(struct Curl_cfilter *cf,
445 struct Curl_easy *data,
446 int query, int *pres1, void *pres2)
447 {
448 struct cf_hc_ctx *ctx = cf->ctx;
449
450 if(!cf->connected) {
451 switch(query) {
452 case CF_QUERY_TIMER_CONNECT: {
453 struct curltime *when = pres2;
454 *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT);
455 return CURLE_OK;
456 }
457 case CF_QUERY_TIMER_APPCONNECT: {
458 struct curltime *when = pres2;
459 *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT);
460 return CURLE_OK;
461 }
462 case CF_QUERY_NEED_FLUSH: {
463 if(cf_hc_baller_needs_flush(&ctx->h3_baller, data)
464 || cf_hc_baller_needs_flush(&ctx->h21_baller, data)) {
465 *pres1 = TRUE;
466 return CURLE_OK;
467 }
468 break;
469 }
470 default:
471 break;
472 }
473 }
474 return cf->next ?
475 cf->next->cft->query(cf->next, data, query, pres1, pres2) :
476 CURLE_UNKNOWN_OPTION;
477 }
478
cf_hc_cntrl(struct Curl_cfilter * cf,struct Curl_easy * data,int event,int arg1,void * arg2)479 static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf,
480 struct Curl_easy *data,
481 int event, int arg1, void *arg2)
482 {
483 struct cf_hc_ctx *ctx = cf->ctx;
484 CURLcode result = CURLE_OK;
485
486 if(!cf->connected) {
487 result = cf_hc_baller_cntrl(&ctx->h3_baller, data, event, arg1, arg2);
488 if(!result || (result == CURLE_AGAIN))
489 result = cf_hc_baller_cntrl(&ctx->h21_baller, data, event, arg1, arg2);
490 if(result == CURLE_AGAIN)
491 result = CURLE_OK;
492 }
493 return result;
494 }
495
cf_hc_close(struct Curl_cfilter * cf,struct Curl_easy * data)496 static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data)
497 {
498 CURL_TRC_CF(data, cf, "close");
499 cf_hc_reset(cf, data);
500 cf->connected = FALSE;
501
502 if(cf->next) {
503 cf->next->cft->do_close(cf->next, data);
504 Curl_conn_cf_discard_chain(&cf->next, data);
505 }
506 }
507
cf_hc_destroy(struct Curl_cfilter * cf,struct Curl_easy * data)508 static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
509 {
510 struct cf_hc_ctx *ctx = cf->ctx;
511
512 (void)data;
513 CURL_TRC_CF(data, cf, "destroy");
514 cf_hc_reset(cf, data);
515 Curl_safefree(ctx);
516 }
517
518 struct Curl_cftype Curl_cft_http_connect = {
519 "HTTPS-CONNECT",
520 0,
521 CURL_LOG_LVL_NONE,
522 cf_hc_destroy,
523 cf_hc_connect,
524 cf_hc_close,
525 cf_hc_shutdown,
526 Curl_cf_def_get_host,
527 cf_hc_adjust_pollset,
528 cf_hc_data_pending,
529 Curl_cf_def_send,
530 Curl_cf_def_recv,
531 cf_hc_cntrl,
532 Curl_cf_def_conn_is_alive,
533 Curl_cf_def_conn_keep_alive,
534 cf_hc_query,
535 };
536
cf_hc_create(struct Curl_cfilter ** pcf,struct Curl_easy * data,const struct Curl_dns_entry * remotehost,bool try_h3,bool try_h21)537 static CURLcode cf_hc_create(struct Curl_cfilter **pcf,
538 struct Curl_easy *data,
539 const struct Curl_dns_entry *remotehost,
540 bool try_h3, bool try_h21)
541 {
542 struct Curl_cfilter *cf = NULL;
543 struct cf_hc_ctx *ctx;
544 CURLcode result = CURLE_OK;
545
546 (void)data;
547 ctx = calloc(1, sizeof(*ctx));
548 if(!ctx) {
549 result = CURLE_OUT_OF_MEMORY;
550 goto out;
551 }
552 ctx->remotehost = remotehost;
553 ctx->h3_baller.enabled = try_h3;
554 ctx->h21_baller.enabled = try_h21;
555
556 result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx);
557 if(result)
558 goto out;
559 ctx = NULL;
560 cf_hc_reset(cf, data);
561
562 out:
563 *pcf = result ? NULL : cf;
564 free(ctx);
565 return result;
566 }
567
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)568 static CURLcode cf_http_connect_add(struct Curl_easy *data,
569 struct connectdata *conn,
570 int sockindex,
571 const struct Curl_dns_entry *remotehost,
572 bool try_h3, bool try_h21)
573 {
574 struct Curl_cfilter *cf;
575 CURLcode result = CURLE_OK;
576
577 DEBUGASSERT(data);
578 result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21);
579 if(result)
580 goto out;
581 Curl_conn_cf_add(data, conn, sockindex, cf);
582 out:
583 return result;
584 }
585
Curl_cf_https_setup(struct Curl_easy * data,struct connectdata * conn,int sockindex,const struct Curl_dns_entry * remotehost)586 CURLcode Curl_cf_https_setup(struct Curl_easy *data,
587 struct connectdata *conn,
588 int sockindex,
589 const struct Curl_dns_entry *remotehost)
590 {
591 bool try_h3 = FALSE, try_h21 = TRUE; /* defaults, for now */
592 CURLcode result = CURLE_OK;
593
594 (void)sockindex;
595 (void)remotehost;
596
597 if(!conn->bits.tls_enable_alpn)
598 goto out;
599
600 if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) {
601 result = Curl_conn_may_http3(data, conn);
602 if(result) /* cannot do it */
603 goto out;
604 try_h3 = TRUE;
605 try_h21 = FALSE;
606 }
607 else if(data->state.httpwant >= CURL_HTTP_VERSION_3) {
608 /* We assume that silently not even trying H3 is ok here */
609 /* TODO: should we fail instead? */
610 try_h3 = (Curl_conn_may_http3(data, conn) == CURLE_OK);
611 try_h21 = TRUE;
612 }
613
614 result = cf_http_connect_add(data, conn, sockindex, remotehost,
615 try_h3, try_h21);
616 out:
617 return result;
618 }
619
620 #endif /* !defined(CURL_DISABLE_HTTP) */
621