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 45typedef enum { 46 CF_HC_INIT, 47 CF_HC_CONNECT, 48 CF_HC_SUCCESS, 49 CF_HC_FAILURE 50} cf_hc_state; 51 52struct cf_hc_baller { 53 const char *name; 54 struct Curl_cfilter *cf; 55 CURLcode result; 56 struct curltime started; 57 int reply_ms; 58 bool enabled; 59}; 60 61static void cf_hc_baller_reset(struct cf_hc_baller *b, 62 struct Curl_easy *data) 63{ 64 if(b->cf) { 65 Curl_conn_cf_close(b->cf, data); 66 Curl_conn_cf_discard_chain(&b->cf, data); 67 b->cf = NULL; 68 } 69 b->result = CURLE_OK; 70 b->reply_ms = -1; 71} 72 73static bool cf_hc_baller_is_active(struct cf_hc_baller *b) 74{ 75 return b->enabled && b->cf && !b->result; 76} 77 78static bool cf_hc_baller_has_started(struct cf_hc_baller *b) 79{ 80 return !!b->cf; 81} 82 83static int cf_hc_baller_reply_ms(struct cf_hc_baller *b, 84 struct Curl_easy *data) 85{ 86 if(b->reply_ms < 0) 87 b->cf->cft->query(b->cf, data, CF_QUERY_CONNECT_REPLY_MS, 88 &b->reply_ms, NULL); 89 return b->reply_ms; 90} 91 92static bool cf_hc_baller_data_pending(struct cf_hc_baller *b, 93 const struct Curl_easy *data) 94{ 95 return b->cf && !b->result && b->cf->cft->has_data_pending(b->cf, data); 96} 97 98struct cf_hc_ctx { 99 cf_hc_state state; 100 const struct Curl_dns_entry *remotehost; 101 struct curltime started; /* when connect started */ 102 CURLcode result; /* overall result */ 103 struct cf_hc_baller h3_baller; 104 struct cf_hc_baller h21_baller; 105 int soft_eyeballs_timeout_ms; 106 int hard_eyeballs_timeout_ms; 107}; 108 109static void cf_hc_baller_init(struct cf_hc_baller *b, 110 struct Curl_cfilter *cf, 111 struct Curl_easy *data, 112 const char *name, 113 int transport) 114{ 115 struct cf_hc_ctx *ctx = cf->ctx; 116 struct Curl_cfilter *save = cf->next; 117 118 b->name = name; 119 cf->next = NULL; 120 b->started = Curl_now(); 121 b->result = Curl_cf_setup_insert_after(cf, data, ctx->remotehost, 122 transport, CURL_CF_SSL_ENABLE); 123 b->cf = cf->next; 124 cf->next = save; 125} 126 127static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b, 128 struct Curl_cfilter *cf, 129 struct Curl_easy *data, 130 bool *done) 131{ 132 struct Curl_cfilter *save = cf->next; 133 134 cf->next = b->cf; 135 b->result = Curl_conn_cf_connect(cf->next, data, FALSE, done); 136 b->cf = cf->next; /* it might mutate */ 137 cf->next = save; 138 return b->result; 139} 140 141static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data) 142{ 143 struct cf_hc_ctx *ctx = cf->ctx; 144 145 if(ctx) { 146 cf_hc_baller_reset(&ctx->h3_baller, data); 147 cf_hc_baller_reset(&ctx->h21_baller, data); 148 ctx->state = CF_HC_INIT; 149 ctx->result = CURLE_OK; 150 ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout; 151 ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2; 152 } 153} 154 155static CURLcode baller_connected(struct Curl_cfilter *cf, 156 struct Curl_easy *data, 157 struct cf_hc_baller *winner) 158{ 159 struct cf_hc_ctx *ctx = cf->ctx; 160 CURLcode result = CURLE_OK; 161 162 DEBUGASSERT(winner->cf); 163 if(winner != &ctx->h3_baller) 164 cf_hc_baller_reset(&ctx->h3_baller, data); 165 if(winner != &ctx->h21_baller) 166 cf_hc_baller_reset(&ctx->h21_baller, data); 167 168 CURL_TRC_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms", 169 winner->name, (int)Curl_timediff(Curl_now(), winner->started), 170 cf_hc_baller_reply_ms(winner, data)); 171 cf->next = winner->cf; 172 winner->cf = NULL; 173 174 switch(cf->conn->alpn) { 175 case CURL_HTTP_VERSION_3: 176 infof(data, "using HTTP/3"); 177 break; 178 case CURL_HTTP_VERSION_2: 179#ifdef USE_NGHTTP2 180 /* Using nghttp2, we add the filter "below" us, so when the conn 181 * closes, we tear it down for a fresh reconnect */ 182 result = Curl_http2_switch_at(cf, data); 183 if(result) { 184 ctx->state = CF_HC_FAILURE; 185 ctx->result = result; 186 return result; 187 } 188#endif 189 infof(data, "using HTTP/2"); 190 break; 191 default: 192 infof(data, "using HTTP/1.x"); 193 break; 194 } 195 ctx->state = CF_HC_SUCCESS; 196 cf->connected = TRUE; 197 Curl_conn_cf_cntrl(cf->next, data, TRUE, 198 CF_CTRL_CONN_INFO_UPDATE, 0, NULL); 199 return result; 200} 201 202 203static bool time_to_start_h21(struct Curl_cfilter *cf, 204 struct Curl_easy *data, 205 struct curltime now) 206{ 207 struct cf_hc_ctx *ctx = cf->ctx; 208 timediff_t elapsed_ms; 209 210 if(!ctx->h21_baller.enabled || cf_hc_baller_has_started(&ctx->h21_baller)) 211 return FALSE; 212 213 if(!ctx->h3_baller.enabled || !cf_hc_baller_is_active(&ctx->h3_baller)) 214 return TRUE; 215 216 elapsed_ms = Curl_timediff(now, ctx->started); 217 if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) { 218 CURL_TRC_CF(data, cf, "hard timeout of %dms reached, starting h21", 219 ctx->hard_eyeballs_timeout_ms); 220 return TRUE; 221 } 222 223 if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) { 224 if(cf_hc_baller_reply_ms(&ctx->h3_baller, data) < 0) { 225 CURL_TRC_CF(data, cf, "soft timeout of %dms reached, h3 has not " 226 "seen any data, starting h21", 227 ctx->soft_eyeballs_timeout_ms); 228 return TRUE; 229 } 230 /* set the effective hard timeout again */ 231 Curl_expire(data, ctx->hard_eyeballs_timeout_ms - elapsed_ms, 232 EXPIRE_ALPN_EYEBALLS); 233 } 234 return FALSE; 235} 236 237static CURLcode cf_hc_connect(struct Curl_cfilter *cf, 238 struct Curl_easy *data, 239 bool blocking, bool *done) 240{ 241 struct cf_hc_ctx *ctx = cf->ctx; 242 struct curltime now; 243 CURLcode result = CURLE_OK; 244 245 (void)blocking; 246 if(cf->connected) { 247 *done = TRUE; 248 return CURLE_OK; 249 } 250 251 *done = FALSE; 252 now = Curl_now(); 253 switch(ctx->state) { 254 case CF_HC_INIT: 255 DEBUGASSERT(!ctx->h3_baller.cf); 256 DEBUGASSERT(!ctx->h21_baller.cf); 257 DEBUGASSERT(!cf->next); 258 CURL_TRC_CF(data, cf, "connect, init"); 259 ctx->started = now; 260 if(ctx->h3_baller.enabled) { 261 cf_hc_baller_init(&ctx->h3_baller, cf, data, "h3", TRNSPRT_QUIC); 262 if(ctx->h21_baller.enabled) 263 Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS); 264 } 265 else if(ctx->h21_baller.enabled) 266 cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", 267 cf->conn->transport); 268 ctx->state = CF_HC_CONNECT; 269 FALLTHROUGH(); 270 271 case CF_HC_CONNECT: 272 if(cf_hc_baller_is_active(&ctx->h3_baller)) { 273 result = cf_hc_baller_connect(&ctx->h3_baller, cf, data, done); 274 if(!result && *done) { 275 result = baller_connected(cf, data, &ctx->h3_baller); 276 goto out; 277 } 278 } 279 280 if(time_to_start_h21(cf, data, now)) { 281 cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21", 282 cf->conn->transport); 283 } 284 285 if(cf_hc_baller_is_active(&ctx->h21_baller)) { 286 CURL_TRC_CF(data, cf, "connect, check h21"); 287 result = cf_hc_baller_connect(&ctx->h21_baller, cf, data, done); 288 if(!result && *done) { 289 result = baller_connected(cf, data, &ctx->h21_baller); 290 goto out; 291 } 292 } 293 294 if((!ctx->h3_baller.enabled || ctx->h3_baller.result) && 295 (!ctx->h21_baller.enabled || ctx->h21_baller.result)) { 296 /* both failed or disabled. we give up */ 297 CURL_TRC_CF(data, cf, "connect, all failed"); 298 result = ctx->result = ctx->h3_baller.enabled? 299 ctx->h3_baller.result : ctx->h21_baller.result; 300 ctx->state = CF_HC_FAILURE; 301 goto out; 302 } 303 result = CURLE_OK; 304 *done = FALSE; 305 break; 306 307 case CF_HC_FAILURE: 308 result = ctx->result; 309 cf->connected = FALSE; 310 *done = FALSE; 311 break; 312 313 case CF_HC_SUCCESS: 314 result = CURLE_OK; 315 cf->connected = TRUE; 316 *done = TRUE; 317 break; 318 } 319 320out: 321 CURL_TRC_CF(data, cf, "connect -> %d, done=%d", result, *done); 322 return result; 323} 324 325static void cf_hc_adjust_pollset(struct Curl_cfilter *cf, 326 struct Curl_easy *data, 327 struct easy_pollset *ps) 328{ 329 if(!cf->connected) { 330 struct cf_hc_ctx *ctx = cf->ctx; 331 struct cf_hc_baller *ballers[2]; 332 size_t i; 333 334 ballers[0] = &ctx->h3_baller; 335 ballers[1] = &ctx->h21_baller; 336 for(i = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) { 337 struct cf_hc_baller *b = ballers[i]; 338 if(!cf_hc_baller_is_active(b)) 339 continue; 340 Curl_conn_cf_adjust_pollset(b->cf, data, ps); 341 } 342 CURL_TRC_CF(data, cf, "adjust_pollset -> %d socks", ps->num); 343 } 344} 345 346static bool cf_hc_data_pending(struct Curl_cfilter *cf, 347 const struct Curl_easy *data) 348{ 349 struct cf_hc_ctx *ctx = cf->ctx; 350 351 if(cf->connected) 352 return cf->next->cft->has_data_pending(cf->next, data); 353 354 CURL_TRC_CF((struct Curl_easy *)data, cf, "data_pending"); 355 return cf_hc_baller_data_pending(&ctx->h3_baller, data) 356 || cf_hc_baller_data_pending(&ctx->h21_baller, data); 357} 358 359static struct curltime cf_get_max_baller_time(struct Curl_cfilter *cf, 360 struct Curl_easy *data, 361 int query) 362{ 363 struct cf_hc_ctx *ctx = cf->ctx; 364 struct Curl_cfilter *cfb; 365 struct curltime t, tmax; 366 367 memset(&tmax, 0, sizeof(tmax)); 368 memset(&t, 0, sizeof(t)); 369 cfb = ctx->h21_baller.enabled? ctx->h21_baller.cf : NULL; 370 if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { 371 if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) 372 tmax = t; 373 } 374 memset(&t, 0, sizeof(t)); 375 cfb = ctx->h3_baller.enabled? ctx->h3_baller.cf : NULL; 376 if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) { 377 if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0) 378 tmax = t; 379 } 380 return tmax; 381} 382 383static CURLcode cf_hc_query(struct Curl_cfilter *cf, 384 struct Curl_easy *data, 385 int query, int *pres1, void *pres2) 386{ 387 if(!cf->connected) { 388 switch(query) { 389 case CF_QUERY_TIMER_CONNECT: { 390 struct curltime *when = pres2; 391 *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT); 392 return CURLE_OK; 393 } 394 case CF_QUERY_TIMER_APPCONNECT: { 395 struct curltime *when = pres2; 396 *when = cf_get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT); 397 return CURLE_OK; 398 } 399 default: 400 break; 401 } 402 } 403 return cf->next? 404 cf->next->cft->query(cf->next, data, query, pres1, pres2) : 405 CURLE_UNKNOWN_OPTION; 406} 407 408static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data) 409{ 410 CURL_TRC_CF(data, cf, "close"); 411 cf_hc_reset(cf, data); 412 cf->connected = FALSE; 413 414 if(cf->next) { 415 cf->next->cft->do_close(cf->next, data); 416 Curl_conn_cf_discard_chain(&cf->next, data); 417 } 418} 419 420static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) 421{ 422 struct cf_hc_ctx *ctx = cf->ctx; 423 424 (void)data; 425 CURL_TRC_CF(data, cf, "destroy"); 426 cf_hc_reset(cf, data); 427 Curl_safefree(ctx); 428} 429 430struct Curl_cftype Curl_cft_http_connect = { 431 "HTTPS-CONNECT", 432 0, 433 CURL_LOG_LVL_NONE, 434 cf_hc_destroy, 435 cf_hc_connect, 436 cf_hc_close, 437 Curl_cf_def_get_host, 438 cf_hc_adjust_pollset, 439 cf_hc_data_pending, 440 Curl_cf_def_send, 441 Curl_cf_def_recv, 442 Curl_cf_def_cntrl, 443 Curl_cf_def_conn_is_alive, 444 Curl_cf_def_conn_keep_alive, 445 cf_hc_query, 446}; 447 448static CURLcode cf_hc_create(struct Curl_cfilter **pcf, 449 struct Curl_easy *data, 450 const struct Curl_dns_entry *remotehost, 451 bool try_h3, bool try_h21) 452{ 453 struct Curl_cfilter *cf = NULL; 454 struct cf_hc_ctx *ctx; 455 CURLcode result = CURLE_OK; 456 457 (void)data; 458 ctx = calloc(1, sizeof(*ctx)); 459 if(!ctx) { 460 result = CURLE_OUT_OF_MEMORY; 461 goto out; 462 } 463 ctx->remotehost = remotehost; 464 ctx->h3_baller.enabled = try_h3; 465 ctx->h21_baller.enabled = try_h21; 466 467 result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx); 468 if(result) 469 goto out; 470 ctx = NULL; 471 cf_hc_reset(cf, data); 472 473out: 474 *pcf = result? NULL : cf; 475 free(ctx); 476 return result; 477} 478 479static CURLcode cf_http_connect_add(struct Curl_easy *data, 480 struct connectdata *conn, 481 int sockindex, 482 const struct Curl_dns_entry *remotehost, 483 bool try_h3, bool try_h21) 484{ 485 struct Curl_cfilter *cf; 486 CURLcode result = CURLE_OK; 487 488 DEBUGASSERT(data); 489 result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21); 490 if(result) 491 goto out; 492 Curl_conn_cf_add(data, conn, sockindex, cf); 493out: 494 return result; 495} 496 497CURLcode Curl_cf_https_setup(struct Curl_easy *data, 498 struct connectdata *conn, 499 int sockindex, 500 const struct Curl_dns_entry *remotehost) 501{ 502 bool try_h3 = FALSE, try_h21 = TRUE; /* defaults, for now */ 503 CURLcode result = CURLE_OK; 504 505 (void)sockindex; 506 (void)remotehost; 507 508 if(!conn->bits.tls_enable_alpn) 509 goto out; 510 511 if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) { 512 result = Curl_conn_may_http3(data, conn); 513 if(result) /* can't do it */ 514 goto out; 515 try_h3 = TRUE; 516 try_h21 = FALSE; 517 } 518 else if(data->state.httpwant >= CURL_HTTP_VERSION_3) { 519 /* We assume that silently not even trying H3 is ok here */ 520 /* TODO: should we fail instead? */ 521 try_h3 = (Curl_conn_may_http3(data, conn) == CURLE_OK); 522 try_h21 = TRUE; 523 } 524 525 result = cf_http_connect_add(data, conn, sockindex, remotehost, 526 try_h3, try_h21); 527out: 528 return result; 529} 530 531#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */ 532