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) && !defined(USE_HYPER)
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
178 DEBUGASSERT(winner->cf);
179 if(winner != &ctx->h3_baller)
180 cf_hc_baller_reset(&ctx->h3_baller, data);
181 if(winner != &ctx->h21_baller)
182 cf_hc_baller_reset(&ctx->h21_baller, data);
183
184 CURL_TRC_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms",
185 winner->name, (int)Curl_timediff(Curl_now(), winner->started),
186 cf_hc_baller_reply_ms(winner, data));
187 cf->next = winner->cf;
188 winner->cf = NULL;
189
190 switch(cf->conn->alpn) {
191 case CURL_HTTP_VERSION_3:
192 break;
193 case CURL_HTTP_VERSION_2:
194 #ifdef USE_NGHTTP2
195 /* Using nghttp2, we add the filter "below" us, so when the conn
196 * closes, we tear it down for a fresh reconnect */
197 result = Curl_http2_switch_at(cf, data);
198 if(result) {
199 ctx->state = CF_HC_FAILURE;
200 ctx->result = result;
201 return result;
202 }
203 #endif
204 break;
205 default:
206 break;
207 }
208 ctx->state = CF_HC_SUCCESS;
209 cf->connected = TRUE;
210 return result;
211 }
212
213
time_to_start_h21(struct Curl_cfilter * cf,struct Curl_easy * data,struct curltime now)214 static bool time_to_start_h21(struct Curl_cfilter *cf,
215 struct Curl_easy *data,
216 struct curltime now)
217 {
218 struct cf_hc_ctx *ctx = cf->ctx;
219 timediff_t elapsed_ms;
220
221 if(!ctx->h21_baller.enabled || cf_hc_baller_has_started(&ctx->h21_baller))
222 return FALSE;
223
224 if(!ctx->h3_baller.enabled || !cf_hc_baller_is_active(&ctx->h3_baller))
225 return TRUE;
226
227 elapsed_ms = Curl_timediff(now, ctx->started);
228 if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) {
229 CURL_TRC_CF(data, cf, "hard timeout of %dms reached, starting h21",
230 ctx->hard_eyeballs_timeout_ms);
231 return TRUE;
232 }
233
234 if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) {
235 if(cf_hc_baller_reply_ms(&ctx->h3_baller, data) < 0) {
236 CURL_TRC_CF(data, cf, "soft timeout of %dms reached, h3 has not "
237 "seen any data, starting h21",
238 ctx->soft_eyeballs_timeout_ms);
239 return TRUE;
240 }
241 /* set the effective hard timeout again */
242 Curl_expire(data, ctx->hard_eyeballs_timeout_ms - elapsed_ms,
243 EXPIRE_ALPN_EYEBALLS);
244 }
245 return FALSE;
246 }
247
cf_hc_connect(struct Curl_cfilter * cf,struct Curl_easy * data,bool blocking,bool * done)248 static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
249 struct Curl_easy *data,
250 bool blocking, bool *done)
251 {
252 struct cf_hc_ctx *ctx = cf->ctx;
253 struct curltime now;
254 CURLcode result = CURLE_OK;
255
256 (void)blocking;
257 if(cf->connected) {
258 *done = TRUE;
259 return CURLE_OK;
260 }
261
262 *done = FALSE;
263 now = Curl_now();
264 switch(ctx->state) {
265 case CF_HC_INIT:
266 DEBUGASSERT(!ctx->h3_baller.cf);
267 DEBUGASSERT(!ctx->h21_baller.cf);
268 DEBUGASSERT(!cf->next);
269 CURL_TRC_CF(data, cf, "connect, init");
270 ctx->started = now;
271 if(ctx->h3_baller.enabled) {
272 cf_hc_baller_init(&ctx->h3_baller, cf, data, "h3", TRNSPRT_QUIC);
273 if(ctx->h21_baller.enabled)
274 Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS);
275 }
276 else if(ctx->h21_baller.enabled)
277 cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21",
278 cf->conn->transport);
279 ctx->state = CF_HC_CONNECT;
280 FALLTHROUGH();
281
282 case CF_HC_CONNECT:
283 if(cf_hc_baller_is_active(&ctx->h3_baller)) {
284 result = cf_hc_baller_connect(&ctx->h3_baller, cf, data, done);
285 if(!result && *done) {
286 result = baller_connected(cf, data, &ctx->h3_baller);
287 goto out;
288 }
289 }
290
291 if(time_to_start_h21(cf, data, now)) {
292 cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21",
293 cf->conn->transport);
294 }
295
296 if(cf_hc_baller_is_active(&ctx->h21_baller)) {
297 CURL_TRC_CF(data, cf, "connect, check h21");
298 result = cf_hc_baller_connect(&ctx->h21_baller, cf, data, done);
299 if(!result && *done) {
300 result = baller_connected(cf, data, &ctx->h21_baller);
301 goto out;
302 }
303 }
304
305 if((!ctx->h3_baller.enabled || ctx->h3_baller.result) &&
306 (!ctx->h21_baller.enabled || ctx->h21_baller.result)) {
307 /* both failed or disabled. we give up */
308 CURL_TRC_CF(data, cf, "connect, all failed");
309 result = ctx->result = ctx->h3_baller.enabled ?
310 ctx->h3_baller.result : ctx->h21_baller.result;
311 ctx->state = CF_HC_FAILURE;
312 goto out;
313 }
314 result = CURLE_OK;
315 *done = FALSE;
316 break;
317
318 case CF_HC_FAILURE:
319 result = ctx->result;
320 cf->connected = FALSE;
321 *done = FALSE;
322 break;
323
324 case CF_HC_SUCCESS:
325 result = CURLE_OK;
326 cf->connected = TRUE;
327 *done = TRUE;
328 break;
329 }
330
331 out:
332 CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done);
333 return result;
334 }
335
cf_hc_shutdown(struct Curl_cfilter * cf,struct Curl_easy * data,bool * done)336 static CURLcode cf_hc_shutdown(struct Curl_cfilter *cf,
337 struct Curl_easy *data, bool *done)
338 {
339 struct cf_hc_ctx *ctx = cf->ctx;
340 struct cf_hc_baller *ballers[2];
341 size_t i;
342 CURLcode result = CURLE_OK;
343
344 DEBUGASSERT(data);
345 if(cf->connected) {
346 *done = TRUE;
347 return CURLE_OK;
348 }
349
350 /* shutdown all ballers that have not done so already. If one fails,
351 * continue shutting down others until all are shutdown. */
352 ballers[0] = &ctx->h3_baller;
353 ballers[1] = &ctx->h21_baller;
354 for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
355 struct cf_hc_baller *b = ballers[i];
356 bool bdone = FALSE;
357 if(!cf_hc_baller_is_active(b) || b->shutdown)
358 continue;
359 b->result = b->cf->cft->do_shutdown(b->cf, data, &bdone);
360 if(b->result || bdone)
361 b->shutdown = TRUE; /* treat a failed shutdown as done */
362 }
363
364 *done = TRUE;
365 for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
366 if(ballers[i] && !ballers[i]->shutdown)
367 *done = FALSE;
368 }
369 if(*done) {
370 for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
371 if(ballers[i] && ballers[i]->result)
372 result = ballers[i]->result;
373 }
374 }
375 CURL_TRC_CF(data, cf, "shutdown -> %d, done=%d", result, *done);
376 return result;
377 }
378
cf_hc_adjust_pollset(struct Curl_cfilter * cf,struct Curl_easy * data,struct easy_pollset * ps)379 static void cf_hc_adjust_pollset(struct Curl_cfilter *cf,
380 struct Curl_easy *data,
381 struct easy_pollset *ps)
382 {
383 if(!cf->connected) {
384 struct cf_hc_ctx *ctx = cf->ctx;
385 struct cf_hc_baller *ballers[2];
386 size_t i;
387
388 ballers[0] = &ctx->h3_baller;
389 ballers[1] = &ctx->h21_baller;
390 for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
391 struct cf_hc_baller *b = ballers[i];
392 if(!cf_hc_baller_is_active(b))
393 continue;
394 Curl_conn_cf_adjust_pollset(b->cf, data, ps);
395 }
396 CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num);
397 }
398 }
399
cf_hc_data_pending(struct Curl_cfilter * cf,const struct Curl_easy * data)400 static bool cf_hc_data_pending(struct Curl_cfilter *cf,
401 const struct Curl_easy *data)
402 {
403 struct cf_hc_ctx *ctx = cf->ctx;
404
405 if(cf->connected)
406 return cf->next->cft->has_data_pending(cf->next, data);
407
408 CURL_TRC_CF((struct Curl_easy *)data, cf, "data_pending");
409 return cf_hc_baller_data_pending(&ctx->h3_baller, data)
410 || cf_hc_baller_data_pending(&ctx->h21_baller, data);
411 }
412
cf_get_max_baller_time(struct Curl_cfilter * cf,struct Curl_easy * data,int query)413 static struct curltime cf_get_max_baller_time(struct Curl_cfilter *cf,
414 struct Curl_easy *data,
415 int query)
416 {
417 struct cf_hc_ctx *ctx = cf->ctx;
418 struct Curl_cfilter *cfb;
419 struct curltime t, tmax;
420
421 memset(&tmax, 0, sizeof(tmax));
422 memset(&t, 0, sizeof(t));
423 cfb = ctx->h21_baller.enabled ? ctx->h21_baller.cf : NULL;
424 if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {
425 if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0)
426 tmax = t;
427 }
428 memset(&t, 0, sizeof(t));
429 cfb = ctx->h3_baller.enabled ? ctx->h3_baller.cf : NULL;
430 if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {
431 if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0)
432 tmax = t;
433 }
434 return tmax;
435 }
436
cf_hc_query(struct Curl_cfilter * cf,struct Curl_easy * data,int query,int * pres1,void * pres2)437 static CURLcode cf_hc_query(struct Curl_cfilter *cf,
438 struct Curl_easy *data,
439 int query, int *pres1, void *pres2)
440 {
441 struct cf_hc_ctx *ctx = cf->ctx;
442
443 if(!cf->connected) {
444 switch(query) {
445 case CF_QUERY_TIMER_CONNECT: {
446 struct curltime *when = pres2;
447 *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT);
448 return CURLE_OK;
449 }
450 case CF_QUERY_TIMER_APPCONNECT: {
451 struct curltime *when = pres2;
452 *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT);
453 return CURLE_OK;
454 }
455 case CF_QUERY_NEED_FLUSH: {
456 if(cf_hc_baller_needs_flush(&ctx->h3_baller, data)
457 || cf_hc_baller_needs_flush(&ctx->h21_baller, data)) {
458 *pres1 = TRUE;
459 return CURLE_OK;
460 }
461 break;
462 }
463 default:
464 break;
465 }
466 }
467 return cf->next ?
468 cf->next->cft->query(cf->next, data, query, pres1, pres2) :
469 CURLE_UNKNOWN_OPTION;
470 }
471
cf_hc_cntrl(struct Curl_cfilter * cf,struct Curl_easy * data,int event,int arg1,void * arg2)472 static CURLcode cf_hc_cntrl(struct Curl_cfilter *cf,
473 struct Curl_easy *data,
474 int event, int arg1, void *arg2)
475 {
476 struct cf_hc_ctx *ctx = cf->ctx;
477 CURLcode result = CURLE_OK;
478
479 if(!cf->connected) {
480 result = cf_hc_baller_cntrl(&ctx->h3_baller, data, event, arg1, arg2);
481 if(!result || (result == CURLE_AGAIN))
482 result = cf_hc_baller_cntrl(&ctx->h21_baller, data, event, arg1, arg2);
483 if(result == CURLE_AGAIN)
484 result = CURLE_OK;
485 }
486 return result;
487 }
488
cf_hc_close(struct Curl_cfilter * cf,struct Curl_easy * data)489 static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data)
490 {
491 CURL_TRC_CF(data, cf, "close");
492 cf_hc_reset(cf, data);
493 cf->connected = FALSE;
494
495 if(cf->next) {
496 cf->next->cft->do_close(cf->next, data);
497 Curl_conn_cf_discard_chain(&cf->next, data);
498 }
499 }
500
cf_hc_destroy(struct Curl_cfilter * cf,struct Curl_easy * data)501 static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
502 {
503 struct cf_hc_ctx *ctx = cf->ctx;
504
505 (void)data;
506 CURL_TRC_CF(data, cf, "destroy");
507 cf_hc_reset(cf, data);
508 Curl_safefree(ctx);
509 }
510
511 struct Curl_cftype Curl_cft_http_connect = {
512 "HTTPS-CONNECT",
513 0,
514 CURL_LOG_LVL_NONE,
515 cf_hc_destroy,
516 cf_hc_connect,
517 cf_hc_close,
518 cf_hc_shutdown,
519 Curl_cf_def_get_host,
520 cf_hc_adjust_pollset,
521 cf_hc_data_pending,
522 Curl_cf_def_send,
523 Curl_cf_def_recv,
524 cf_hc_cntrl,
525 Curl_cf_def_conn_is_alive,
526 Curl_cf_def_conn_keep_alive,
527 cf_hc_query,
528 };
529
cf_hc_create(struct Curl_cfilter ** pcf,struct Curl_easy * data,const struct Curl_dns_entry * remotehost,bool try_h3,bool try_h21)530 static CURLcode cf_hc_create(struct Curl_cfilter **pcf,
531 struct Curl_easy *data,
532 const struct Curl_dns_entry *remotehost,
533 bool try_h3, bool try_h21)
534 {
535 struct Curl_cfilter *cf = NULL;
536 struct cf_hc_ctx *ctx;
537 CURLcode result = CURLE_OK;
538
539 (void)data;
540 ctx = calloc(1, sizeof(*ctx));
541 if(!ctx) {
542 result = CURLE_OUT_OF_MEMORY;
543 goto out;
544 }
545 ctx->remotehost = remotehost;
546 ctx->h3_baller.enabled = try_h3;
547 ctx->h21_baller.enabled = try_h21;
548
549 result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx);
550 if(result)
551 goto out;
552 ctx = NULL;
553 cf_hc_reset(cf, data);
554
555 out:
556 *pcf = result ? NULL : cf;
557 free(ctx);
558 return result;
559 }
560
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)561 static CURLcode cf_http_connect_add(struct Curl_easy *data,
562 struct connectdata *conn,
563 int sockindex,
564 const struct Curl_dns_entry *remotehost,
565 bool try_h3, bool try_h21)
566 {
567 struct Curl_cfilter *cf;
568 CURLcode result = CURLE_OK;
569
570 DEBUGASSERT(data);
571 result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21);
572 if(result)
573 goto out;
574 Curl_conn_cf_add(data, conn, sockindex, cf);
575 out:
576 return result;
577 }
578
Curl_cf_https_setup(struct Curl_easy * data,struct connectdata * conn,int sockindex,const struct Curl_dns_entry * remotehost)579 CURLcode Curl_cf_https_setup(struct Curl_easy *data,
580 struct connectdata *conn,
581 int sockindex,
582 const struct Curl_dns_entry *remotehost)
583 {
584 bool try_h3 = FALSE, try_h21 = TRUE; /* defaults, for now */
585 CURLcode result = CURLE_OK;
586
587 (void)sockindex;
588 (void)remotehost;
589
590 if(!conn->bits.tls_enable_alpn)
591 goto out;
592
593 if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) {
594 result = Curl_conn_may_http3(data, conn);
595 if(result) /* cannot do it */
596 goto out;
597 try_h3 = TRUE;
598 try_h21 = FALSE;
599 }
600 else if(data->state.httpwant >= CURL_HTTP_VERSION_3) {
601 /* We assume that silently not even trying H3 is ok here */
602 /* TODO: should we fail instead? */
603 try_h3 = (Curl_conn_may_http3(data, conn) == CURLE_OK);
604 try_h21 = TRUE;
605 }
606
607 result = cf_http_connect_add(data, conn, sockindex, remotehost,
608 try_h3, try_h21);
609 out:
610 return result;
611 }
612
613 #endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */
614